diff --git a/.github/workflows/publish_testpypi.yml b/.github/workflows/publish_testpypi.yml
index be56130a..73de2001 100644
--- a/.github/workflows/publish_testpypi.yml
+++ b/.github/workflows/publish_testpypi.yml
@@ -12,7 +12,7 @@ jobs:
max-parallel: 2
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
- python_v: ['3.8', '3.9', '3.10', '']
+ python_v: ['3.9', '3.10', '3.11', '']
# chrome_v: ['-1']
defaults:
run:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1aba10fe..35e27aaa 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,6 +11,9 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
+ env:
+ PYTHONTRACEMALLOC: "1"
+ PYTHONWARNINGS: "error::ResourceWarning"
defaults:
run:
working-directory: ./src/py/
diff --git a/.gitignore b/.gitignore
index 068efd7f..31e47007 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,4 @@ src/py/integration_tests/report*
node_modules/
src/py/site/*
+.hypothesis/
diff --git a/src/py/.pre-commit-config.yaml b/src/py/.pre-commit-config.yaml
index 5f875d93..41499ae6 100644
--- a/src/py/.pre-commit-config.yaml
+++ b/src/py/.pre-commit-config.yaml
@@ -14,7 +14,7 @@ default_install_hook_types: [pre-commit, commit-msg]
default_stages: [pre-commit]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v3.2.0
+ rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -25,12 +25,12 @@ repos:
- id: check-toml
- id: debug-statements
- repo: https://github.com/asottile/add-trailing-comma
- rev: v3.1.0
+ rev: v3.2.0
hooks:
- id: add-trailing-comma
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
- rev: v0.12.10
+ rev: v0.13.3
hooks:
# Run the linter.
- id: ruff
@@ -40,7 +40,7 @@ repos:
types_or: [python, pyi]
# options: ignore one line things [E701]
- repo: https://github.com/adrienverge/yamllint
- rev: v1.35.1
+ rev: v1.37.1
hooks:
- id: yamllint
name: yamllint
@@ -55,7 +55,7 @@ repos:
}\
}"]
- repo: https://github.com/rhysd/actionlint
- rev: v1.7.4
+ rev: v1.7.7
hooks:
- id: actionlint
name: Lint GitHub Actions workflow files
@@ -76,18 +76,9 @@ repos:
args: [--staged, -c, "general.ignore=B6,T3", --msg-filename]
stages: [commit-msg]
- repo: https://github.com/crate-ci/typos
- rev: v1.28.2
+ rev: v1
hooks:
- id: typos
- - repo: https://github.com/markdownlint/markdownlint
- rev: v0.13.0
- hooks:
- - id: markdownlint
- name: Markdownlint
- description: Run markdownlint on your Markdown files
- entry: mdl --rules ~MD026 --style .markdown.rb
- language: ruby
- files: \.(md|mdown|markdown)$
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
@@ -96,3 +87,16 @@ repos:
language: python
entry: detect-secrets-hook
args: ['']
+ - repo: https://github.com/rvben/rumdl-pre-commit
+ rev: v0.0.153 # Use the latest release tag
+ hooks:
+ - id: rumdl
+ # To only check (default):
+ # args: []
+ # To automatically fix issues:
+ # args: [--fix]
+ - repo: https://github.com/RobertCraigie/pyright-python
+ rev: v1.1.406 # pin a tag; latest as of 2025-10-01
+ hooks:
+ - id: pyright
+ args: ["--project=src/py"]
diff --git a/src/py/CHANGELOG.txt b/src/py/CHANGELOG.txt
index 57b4de61..a9a25322 100644
--- a/src/py/CHANGELOG.txt
+++ b/src/py/CHANGELOG.txt
@@ -1,3 +1,11 @@
+v1.3.0
+- Significant refactor, better organization
+- `write_fig` and `_from_object` now take an additional argument:
+ `fail_on_error: bool`. If False, default True, returns a list of errors.
+- Unused `path` argument for `calc_fig` was removed.
+- Fixed race condition where two render tasks would choose the same filename
+
+
v1.2.0
- Try to use plotly JSON encoder instead of default
diff --git a/src/py/kaleido/__init__.py b/src/py/kaleido/__init__.py
index 71c18427..900e40b9 100644
--- a/src/py/kaleido/__init__.py
+++ b/src/py/kaleido/__init__.py
@@ -19,11 +19,13 @@
from pathlib import Path
from typing import Any, TypeVar, Union
- from ._fig_tools import Figurish, LayoutOpts
+ from ._utils.fig_tools import Figurish, LayoutOpts
T = TypeVar("T")
AnyIterable = Union[AsyncIterable[T], Iterable[T]]
+ from .kaleido import FigureDict
+
__all__ = [
"Kaleido",
"PageGenerator",
@@ -50,7 +52,7 @@ def start_sync_server(*args: Any, silence_warnings: bool = False, **kwargs: Any)
function will warn you if the server is already running.
This wrapper function takes the exact same arguments as kaleido.Kaleido(),
- except one extra, `silence_warnings`.
+ except one extra: `silence_warnings`.
Args:
*args: all arguments `Kaleido()` would take.
@@ -64,11 +66,13 @@ def start_sync_server(*args: Any, silence_warnings: bool = False, **kwargs: Any)
def stop_sync_server(*, silence_warnings: bool = False):
"""
- Stop the kaleido server. It can be restarted. Warns if not started.
+ Stop the kaleido server. It can be restarted.
+
+ This function will warn you if the server is already stopped.
Args:
silence_warnings: (bool, default False): If True, don't emit warning if
- stopping a server that's not running.
+ stopping an already stopped server.
"""
_global_server.close(silence_warnings=silence_warnings)
@@ -76,7 +80,6 @@ def stop_sync_server(*, silence_warnings: bool = False):
async def calc_fig(
fig: Figurish,
- path: str | None | Path = None,
opts: LayoutOpts | None = None,
*,
topojson: str | None = None,
@@ -86,14 +89,13 @@ async def calc_fig(
Return binary for plotly figure.
A convenience wrapper for `Kaleido.calc_fig()` which starts a `Kaleido` and
- executes the `calc_fig()`.
+ executes `calc_fig()`.
It takes an additional argument, `kopts`, a dictionary of arguments to pass
to the kaleido process. See the `kaleido.Kaleido` docs. However,
`calc_fig()` will never use more than one processor, so any `n` value will
be overridden.
-
- See documentation for `Kaleido.calc_fig()`.
+ See also the documentation for `Kaleido.calc_fig()`.
"""
kopts = kopts or {}
@@ -101,7 +103,6 @@ async def calc_fig(
async with Kaleido(**kopts) as k:
return await k.calc_fig(
fig,
- path=path,
opts=opts,
topojson=topojson,
)
@@ -122,14 +123,13 @@ async def write_fig(
A convenience wrapper for `Kaleido.write_fig()` which starts a `Kaleido` and
executes the `write_fig()`.
It takes an additional argument, `kopts`, a dictionary of arguments to pass
- to the kaleido process. See the `kaleido.Kaleido` docs.
-
+ to the `Kaleido` constructor. See the `kaleido.Kaleido` docs.
- See documentation for `Kaleido.write_fig()` for the other arguments.
+ See also the documentation for `Kaleido.write_fig()`.
"""
async with Kaleido(**(kopts or {})) as k:
- await k.write_fig(
+ return await k.write_fig(
fig,
path=path,
opts=opts,
@@ -139,26 +139,25 @@ async def write_fig(
async def write_fig_from_object(
- generator: AnyIterable, # this could be more specific with []
+ fig_dicts: FigureDict | AnyIterable[FigureDict],
*,
kopts: dict[str, Any] | None = None,
**kwargs,
):
"""
- Write a plotly figure(s) to a file.
+ Write a plotly figure(s) to a file specified by a dictionary or iterable of.
A convenience wrapper for `Kaleido.write_fig_from_object()` which starts a
`Kaleido` and executes the `write_fig_from_object()`
It takes an additional argument, `kopts`, a dictionary of arguments to pass
- to the kaleido process. See the `kaleido.Kaleido` docs.
+ to the `Kaleido` constructor. See the `kaleido.Kaleido` docs.
- See documentation for `Kaleido.write_fig_from_object()` for the other
- arguments.
+ See also the documentation for `Kaleido.write_fig_from_object()`.
"""
async with Kaleido(**(kopts or {})) as k:
- await k.write_fig_from_object(
- generator,
+ return await k.write_fig_from_object(
+ fig_dicts,
**kwargs,
)
@@ -174,14 +173,18 @@ def calc_fig_sync(*args: Any, **kwargs: Any):
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)
+ return _global_server.call_function("write_fig", *args, **kwargs)
else:
- _sync_server.oneshot_async_run(write_fig, args=args, kwargs=kwargs)
+ return _sync_server.oneshot_async_run(write_fig, args=args, kwargs=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)
+ return _global_server.call_function("write_fig_from_object", *args, **kwargs)
else:
- _sync_server.oneshot_async_run(write_fig_from_object, args=args, kwargs=kwargs)
+ return _sync_server.oneshot_async_run(
+ write_fig_from_object,
+ args=args,
+ kwargs=kwargs,
+ )
diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py
deleted file mode 100644
index 30701808..00000000
--- a/src/py/kaleido/_fig_tools.py
+++ /dev/null
@@ -1,227 +0,0 @@
-"""
-Adapted from old code, it 1. validates, 2. write defaults, 3. packages object.
-
-Its a bit complicated and mixed in order.
-"""
-
-from __future__ import annotations
-
-import glob
-import re
-from pathlib import Path
-from typing import TYPE_CHECKING, Literal, TypedDict
-
-import logistro
-
-if TYPE_CHECKING:
- from typing import Any
-
- from typing_extensions import TypeGuard
-
- Figurish = Any # Be nice to make it more specific, dictionary or something
- FormatString = Literal["png", "jpg", "jpeg", "webp", "svg", "json", "pdf"]
-
-_logger = logistro.getLogger(__name__)
-
-# constants
-DEFAULT_EXT = "png"
-DEFAULT_SCALE = 1
-DEFAULT_WIDTH = 700
-DEFAULT_HEIGHT = 500
-SUPPORTED_FORMATS: tuple[FormatString, ...] = (
- "png",
- "jpg",
- "jpeg",
- "webp",
- "svg",
- "json",
- "pdf",
-)
-
-
-def _assert_format(ext: str) -> TypeGuard[FormatString]:
- if ext not in SUPPORTED_FORMATS:
- raise ValueError(
- f"Invalid format '{ext}'.\n Supported formats: {SUPPORTED_FORMATS!s}",
- )
- return True
-
-
-def _is_figurish(o: Any) -> TypeGuard[Figurish]:
- valid = hasattr(o, "to_dict") or (isinstance(o, dict) and "data" in o)
- if not valid:
- _logger.debug(
- f"Figure has to_dict? {hasattr(o, 'to_dict')} "
- f"is dict? {isinstance(o, dict)} "
- f"Keys: {o.keys() if hasattr(o, 'keys') else None!s}",
- )
- return valid
-
-
-def _get_figure_dimensions(
- layout: dict,
- width: float | None,
- height: float | None,
-) -> tuple[float, float]:
- # Compute image width / height with fallbacks
- width = (
- width
- or layout.get("width")
- or layout.get("template", {}).get("layout", {}).get("width")
- or DEFAULT_WIDTH
- )
- height = (
- height
- or layout.get("height")
- or layout.get("template", {}).get("layout", {}).get("height")
- or DEFAULT_HEIGHT
- )
- return width, height
-
-
-def _get_format(extension: str) -> FormatString:
- formatted_extension = extension.lower()
- if formatted_extension == "jpg":
- return "jpeg"
- if not _assert_format(formatted_extension):
- raise ValueError # this line will never be reached its for typer
- return formatted_extension
-
-
-# Input of to_spec (user gives us this)
-class LayoutOpts(TypedDict, total=False):
- format: FormatString | None
- scale: int | float
- height: int | float
- width: int | float
-
-
-# Output of to_spec (we give kaleido_scopes.js this)
-# refactor note: this could easily be right before send
-class Spec(TypedDict):
- format: FormatString
- width: int | float
- height: int | float
- scale: int | float
- data: Figurish
-
-
-# validate configuration options for kaleido.js and package like its wants
-def to_spec(figure: Figurish, layout_opts: LayoutOpts) -> Spec:
- # Get figure layout
- layout = figure.get("layout", {})
-
- for k, v in layout_opts.items():
- if k == "format":
- if v is not None and not isinstance(v, (str)):
- raise TypeError(
- f"{k} must be one of {SUPPORTED_FORMATS!s} or None, not {v}.",
- )
- elif k in ("scale", "height", "width"):
- if v is not None and not isinstance(v, (float, int)):
- raise TypeError(f"{k} must be numeric or None, not {v}.")
- else:
- raise AttributeError(f"Unknown key in layout options, {k}")
-
- # Extract info
- extension = _get_format(layout_opts.get("format") or DEFAULT_EXT)
-
- width, height = _get_figure_dimensions(
- layout,
- layout_opts.get("width"),
- layout_opts.get("height"),
- )
- scale = layout_opts.get("scale", DEFAULT_SCALE)
-
- return {
- "format": extension,
- "width": width,
- "height": height,
- "scale": scale,
- "data": figure,
- }
-
-
-# if we need to suffix the filename automatically:
-def _next_filename(path: Path | str, prefix: str, ext: str) -> str:
- path = path if isinstance(path, Path) else Path(path)
- default = 1 if (path / f"{prefix}.{ext}").exists() else 0
- re_number = re.compile(
- r"^" + re.escape(prefix) + r"\-(\d+)\." + re.escape(ext) + r"$",
- )
- escaped_prefix = glob.escape(prefix)
- escaped_ext = glob.escape(ext)
- numbers = [
- int(match.group(1))
- for name in path.glob(f"{escaped_prefix}-*.{escaped_ext}")
- if (match := re_number.match(Path(name).name))
- ]
- n = max(numbers, default=default) + 1
- return f"{prefix}.{ext}" if n == 1 else f"{prefix}-{n}.{ext}"
-
-
-# validate and build full route if needed:
-def _build_full_path(
- path: Path | None,
- fig: Figurish,
- ext: FormatString,
-) -> Path:
- full_path: Path | None = None
-
- directory: Path
-
- if not path:
- directory = Path() # use current Path
- elif path and (not path.suffix or path.is_dir()):
- if not path.is_dir():
- raise ValueError(f"Directory {path} not found. Please create it.")
- directory = path
- else:
- full_path = path
- if not full_path.parent.is_dir():
- raise RuntimeError(
- f"Cannot reach path {path.parent}. Are all directories created?",
- )
-
- if not full_path:
- _logger.debug("Looking for title")
- prefix = fig.get("layout", {}).get("title", {}).get("text", "fig")
- prefix = re.sub(r"[ \-]", "_", prefix)
- prefix = re.sub(r"[^a-zA-Z0-9_]", "", prefix)
- prefix = prefix or "fig"
- _logger.debug(f"Found: {prefix}")
- name = _next_filename(directory, prefix, ext)
- full_path = directory / name
- return full_path
-
-
-# call all validators/automatic config fill-in/packaging in expected format
-def build_fig_spec(
- fig: Figurish,
- path: Path | str | None,
- opts: LayoutOpts | None,
-) -> tuple[Spec, Path]:
- if not opts:
- opts = {}
-
- if not _is_figurish(fig):
- raise TypeError("Figure supplied doesn't seem to be a valid plotly figure.")
-
- if hasattr(fig, "to_dict"):
- fig = fig.to_dict()
-
- if isinstance(path, str):
- path = Path(path)
- elif path and not isinstance(path, Path):
- raise TypeError("Path should be a string or `pathlib.Path` object (or None)")
-
- if not opts.get("format") and path and path.suffix:
- ext = path.suffix.lstrip(".")
- if _assert_format(ext): # not strict necessary if but helps typeguard
- opts["format"] = ext
-
- spec = to_spec(fig, opts)
-
- full_path = _build_full_path(path, fig, spec["format"])
-
- return spec, full_path
diff --git a/src/py/kaleido/_kaleido_tab.py b/src/py/kaleido/_kaleido_tab.py
deleted file mode 100644
index 6288fa06..00000000
--- a/src/py/kaleido/_kaleido_tab.py
+++ /dev/null
@@ -1,380 +0,0 @@
-from __future__ import annotations
-
-import base64
-import json
-import time
-from typing import TYPE_CHECKING
-
-import logistro
-from choreographer.errors import DevtoolsProtocolError
-
-from ._utils import ErrorEntry, to_thread
-
-if TYPE_CHECKING:
- from pathlib import Path
- from typing import Any
-
- import choreographer as choreo
-
-_logger = logistro.getLogger(__name__)
-
-_TEXT_FORMATS = ("svg", "json") # eps
-
-
-class JavascriptError(RuntimeError): # TODO(A): process better # noqa: TD003, FIX002
- """Used to report errors from javascript."""
-
-
-### Error definitions ###
-class KaleidoError(Exception):
- """An error to interpret errors from Kaleido's JS side."""
-
- def __init__(self, code, message):
- """
- Construct an error object.
-
- Args:
- code: the number code of the error.
- message: the message of the error.
-
- """
- super().__init__(message)
- self._code = code
- self._message = message
-
- def __str__(self):
- """Display the KaleidoError nicely."""
- return f"Error {self._code}: {self._message}"
-
-
-def _check_error(result):
- e = _check_error_ret(result)
- if e:
- raise e
-
-
-def _check_error_ret(result): # Utility
- """Check browser response for errors. Helper function."""
- if "error" in result:
- return DevtoolsProtocolError(result)
- if result.get("result", {}).get("result", {}).get("subtype", None) == "error":
- return JavascriptError(str(result.get("result")))
- return None
-
-
-def _make_console_logger(name, log):
- """Create printer specifically for console events. Helper function."""
-
- async def console_printer(event):
- _logger.debug2(f"{name}:{event}") # TODO(A): parse # noqa: TD003, FIX002
- log.append(str(event))
-
- return console_printer
-
-
-class _KaleidoTab:
- """
- A Kaleido tab is a wrapped choreographer tab providing the functions we need.
-
- The choreographer tab can be access through the `self.tab` attribute.
- """
-
- tab: choreo.Tab
- """The underlying choreographer tab."""
-
- javascript_log: list[Any]
- """A list of console outputs from the tab."""
-
- def __init__(self, tab, *, _stepper=False):
- """
- Create a new _KaleidoTab.
-
- Args:
- tab: the choreographer tab to wrap.
-
- """
- self.tab = tab
- self.javascript_log = []
- self._stepper = _stepper
-
- def _regenerate_javascript_console(self):
- tab = self.tab
- self.javascript_log = []
- _logger.debug2("Subscribing to all console prints for tab {tab}.")
- tab.unsubscribe("Runtime.consoleAPICalled")
- tab.subscribe(
- "Runtime.consoleAPICalled",
- _make_console_logger("tab js console", self.javascript_log),
- )
-
- async def navigate(self, url: str | Path = ""):
- """
- Navigate to the kaleidofier script. This is effectively the real initialization.
-
- Args:
- url: Override the location of the kaleidofier script if necessary.
-
- """
- tab = self.tab
- javascript_ready = tab.subscribe_once("Runtime.executionContextCreated")
- while javascript_ready.done():
- _logger.debug2("Clearing an old Runtime.executionContextCreated")
- javascript_ready = tab.subscribe_once("Runtime.executionContextCreated")
- page_ready = tab.subscribe_once("Page.loadEventFired")
- while page_ready.done():
- _logger.debug2("Clearing a old Page.loadEventFired")
- page_ready = tab.subscribe_once("Page.loadEventFired")
-
- _logger.debug2(f"Calling Page.navigate on {tab}")
- _check_error(await tab.send_command("Page.navigate", params={"url": url}))
- # Must enable after navigating.
- _logger.debug2(f"Calling Page.enable on {tab}")
- _check_error(await tab.send_command("Page.enable"))
- _logger.debug2(f"Calling Runtime.enable on {tab}")
- _check_error(await tab.send_command("Runtime.enable"))
-
- await javascript_ready
- self._current_js_id = (
- javascript_ready.result()
- .get("params", {})
- .get("context", {})
- .get("id", None)
- )
- if not self._current_js_id:
- raise RuntimeError(
- "Refresh sequence didn't work for reload_tab_with_javascript."
- "Result {javascript_ready.result()}.",
- )
- await page_ready
- self._regenerate_javascript_console()
-
- async def reload(self):
- """Reload the tab, and set the javascript runtime id."""
- tab = self.tab
- _logger.debug(f"Reloading tab {tab} with javascript.")
- javascript_ready = tab.subscribe_once("Runtime.executionContextCreated")
- while javascript_ready.done():
- _logger.debug2("Clearing an old Runtime.executionContextCreated")
- javascript_ready = tab.subscribe_once("Runtime.executionContextCreated")
- is_loaded = tab.subscribe_once("Page.loadEventFired")
- while is_loaded.done():
- _logger.debug2("Clearing an old Page.loadEventFired")
- is_loaded = tab.subscribe_once("Page.loadEventFired")
- _logger.debug2(f"Calling Page.reload on {tab}")
- _check_error(await tab.send_command("Page.reload"))
- await javascript_ready
- self._current_js_id = (
- javascript_ready.result()
- .get("params", {})
- .get("context", {})
- .get("id", None)
- )
- if not self._current_js_id:
- raise RuntimeError(
- "Refresh sequence didn't work for reload_tab_with_javascript."
- "Result {javascript_ready.result()}.",
- )
- await is_loaded
- self._regenerate_javascript_console()
-
- async def console_print(self, message: str) -> None:
- """
- Print something to the javascript console.
-
- Args:
- message: The thing to print.
-
- """
- jsfn = r"function()" r"{" f"console.log('{message}')" r"}"
- params = {
- "functionDeclaration": jsfn,
- "returnByValue": False,
- "userGesture": True,
- "awaitPromise": True,
- "executionContextId": self._current_js_id,
- }
-
- # send request to run script in chromium
- _logger.debug("Calling js function")
- result = await self.tab.send_command("Runtime.callFunctionOn", params=params)
- _logger.debug(f"Sent javascript got result: {result}")
- _check_error(result)
-
- def _finish_profile(self, profile, state, error=None):
- _logger.debug("Finishing profile")
- profile["duration"] = float(f"{time.perf_counter() - profile['start']:.6f}")
- del profile["start"]
- profile["state"] = state
- if self.javascript_log:
- profile["js_console"] = self.javascript_log
- if error:
- profile["error"] = error
-
- async def _write_fig(
- self,
- spec,
- full_path,
- *,
- topojson=None,
- error_log=None,
- profiler=None,
- ):
- """Calculate and write figure to file. Wraps _calc_fig, and writes a file."""
- img, profile = await self._calc_fig(
- spec,
- full_path,
- topojson=topojson,
- error_log=error_log,
- profiler=profiler,
- )
-
- def write_image(binary):
- with full_path.open("wb") as file:
- file.write(binary)
-
- _logger.info(f"Starting write of {full_path.name}")
- await to_thread(write_image, img)
- _logger.info(f"Wrote {full_path.name}")
-
- if profile is not None:
- profile["megabytes"] = full_path.stat().st_size / 1000000
- profile["state"] = "WROTE"
-
- async def _calc_fig( # noqa: C901, PLR0912, complexity, branches
- self,
- spec,
- full_path,
- *,
- topojson=None,
- error_log=None,
- profiler=None,
- ):
- """
- Call the plotly renderer via javascript.
-
- Args:
- spec: the processed plotly figure
- full_path: the path to write the image too. if its a directory, we will try
- to generate a name. If the path contains an extension,
- "path/to/my_image.png", that extension will be the format used if not
- overridden in `opts`.
- opts: dictionary describing format, width, height, and scale of image
- topojson: topojsons are used to customize choropleths
- error_log: A supplied list, will be populated with `ErrorEntry`s
- which can be converted to strings. Note, this is for
- collections errors that have to do with plotly. They will
- not be thrown. Lower level errors (kaleido, choreographer)
- will still be thrown. If not passed, all errors raise.
- profiler: a supplied dictionary to collect stats about the operation
-
- """
- tab = self.tab
- execution_context_id = self._current_js_id
- if profiler is not None:
- profile = {
- "name": full_path.name,
- "start": time.perf_counter(),
- "state": "INIT",
- }
- else:
- profile = None
-
- _logger.debug(f"In tab {tab.target_id[:4]} calc_fig for {full_path.name}.")
-
- _logger.info(f"Processing {full_path.name}")
- # js script
- kaleido_jsfn = (
- r"function(spec, ...args)"
- r"{"
- r"return kaleido_scopes.plotly(spec, ...args).then(JSON.stringify);"
- r"}"
- )
-
- # params
- arguments = [{"value": spec}]
- arguments.append({"value": topojson if topojson else None})
- arguments.append({"value": self._stepper})
- params = {
- "functionDeclaration": kaleido_jsfn,
- "arguments": arguments,
- "returnByValue": False,
- "userGesture": True,
- "awaitPromise": True,
- "executionContextId": execution_context_id,
- }
-
- _logger.info(f"Sending big command for {full_path.name}.")
- if profile:
- profile["state"] = "SENDING"
- result = await tab.send_command("Runtime.callFunctionOn", params=params)
- if profile:
- profile["state"] = "SENT"
- _logger.info(f"Sent big command for {full_path.name}.")
- e = _check_error_ret(result)
- if e:
- if profiler is not None:
- self._finish_profile(profile, "ERROR", e)
- profiler[tab.target_id].append(profile)
- if error_log is not None:
- error_log.append(ErrorEntry(full_path.name, e, self.javascript_log))
- _logger.error(f"Failed {full_path.name}", exc_info=e)
- else:
- _logger.error(f"Raising error on {full_path.name}")
- raise e
- _logger.debug2(f"Result of function call: {result}")
- if self._stepper:
- print(f"Image {full_path.name} was sent to browser") # noqa: T201
- input("Press Enter to continue...")
- if e:
- return None, None
-
- img = await self._img_from_response(result)
- if isinstance(img, BaseException):
- if profiler is not None:
- self._finish_profile(profile, "ERROR", img)
- profiler[tab.target_id].append(profile)
- if error_log is not None:
- error_log.append(
- ErrorEntry(full_path.name, img, self.javascript_log),
- )
- _logger.info(f"Failed {full_path.name}")
- return None, None
- else:
- raise img
- if profile:
- self._finish_profile(profile, "CALCULATED", None)
- profiler[tab.target_id].append(profile)
- return img, profile
-
- async def _img_from_response(self, response):
- js_response = json.loads(response.get("result").get("result").get("value"))
-
- if js_response["code"] != 0:
- return KaleidoError(js_response["code"], js_response["message"])
-
- response_format = js_response.get("format")
- img = js_response.get("result")
- if response_format == "pdf":
- pdf_params = {
- "printBackground": True,
- "marginTop": 0.1,
- "marginBottom": 0.1,
- "marginLeft": 0.1,
- "marginRight": 0.1,
- "preferCSSPageSize": True,
- "pageRanges": "1",
- }
- pdf_response = await self.tab.send_command(
- "Page.printToPDF",
- params=pdf_params,
- )
- e = _check_error_ret(pdf_response)
- if e:
- return e
- img = pdf_response.get("result").get("data")
- # Base64 decode binary types
- if response_format not in _TEXT_FORMATS:
- img = base64.b64decode(img)
- else:
- img = str.encode(img)
- return img
diff --git a/src/py/kaleido/_kaleido_tab/__init__.py b/src/py/kaleido/_kaleido_tab/__init__.py
new file mode 100644
index 00000000..1828d1be
--- /dev/null
+++ b/src/py/kaleido/_kaleido_tab/__init__.py
@@ -0,0 +1,8 @@
+from ._errors import JavascriptError, KaleidoError
+from ._tab import _KaleidoTab
+
+__all__ = [
+ "JavascriptError",
+ "KaleidoError",
+ "_KaleidoTab",
+]
diff --git a/src/py/kaleido/_kaleido_tab/_devtools_utils.py b/src/py/kaleido/_kaleido_tab/_devtools_utils.py
new file mode 100644
index 00000000..813c98f3
--- /dev/null
+++ b/src/py/kaleido/_kaleido_tab/_devtools_utils.py
@@ -0,0 +1,125 @@
+"""Contains both DevTools protocol and kaleido_scopes.js helper fns."""
+
+from __future__ import annotations
+
+import json
+from typing import TYPE_CHECKING
+
+import logistro
+
+from ._errors import KaleidoError, _raise_error
+
+if TYPE_CHECKING:
+ from typing import Any
+
+ import choreographer
+
+_logger = logistro.getLogger(__name__)
+
+
+def get_js_id(result) -> int:
+ """Grab javascript engine ID from a chrome executionContextStarted event."""
+ if _id := result.get("params", {}).get("context", {}).get("id", None):
+ return _id
+ raise RuntimeError(
+ "Refresh sequence didn't work for reload_tab_with_javascript."
+ "Result {javascript_ready.result()}.",
+ )
+
+
+async def console_print(tab: choreographer.Tab, js_id: int, message: str) -> None:
+ """
+ Print something to the javascript console.
+
+ Note: tab and js_id need to specified separately
+
+ Args:
+ tab: the choreographer tab to print on
+ js_id: the javascript engine id to print on
+ message: The thing to print.
+
+ """
+ fn = r"function()" r"{" f"console.log('{message}')" r"}"
+ params = {
+ "functionDeclaration": fn,
+ "returnByValue": False,
+ "userGesture": True,
+ "awaitPromise": True,
+ "executionContextId": js_id,
+ }
+
+ # send request to run script in chromium
+ _logger.debug("Calling js function")
+ result = await tab.send_command("Runtime.callFunctionOn", params=params)
+ _logger.debug(f"Sent javascript got result: {result}")
+ _raise_error(result)
+
+
+async def exec_js_fn(
+ tab: choreographer.Tab,
+ js_id: int,
+ fn: str,
+ *args: Any,
+):
+ args_structured = [{"value": arg} for arg in args]
+ params = {
+ "functionDeclaration": fn,
+ "arguments": args_structured,
+ "returnByValue": False,
+ "userGesture": True,
+ "awaitPromise": True,
+ "executionContextId": js_id,
+ }
+ return await tab.send_command("Runtime.callFunctionOn", params=params)
+
+
+def check_kaleido_js_response(
+ response,
+) -> dict:
+ _raise_error(response)
+ js_response = json.loads(
+ response.get(
+ "result",
+ {},
+ )
+ .get(
+ "result",
+ {},
+ )
+ .get(
+ "value",
+ ),
+ )
+ if not js_response:
+ raise RuntimeError(
+ f"Javascript response not understood: {response}",
+ )
+ # NOTE: Why above "JavascriptError"
+ # This is an error in extracting a response when we expected javascript.
+ # If its an actual javascript error, the response will be coherent, and
+ # _raise_error above will find it.
+
+ if js_response["code"] != 0:
+ raise KaleidoError(js_response["code"], js_response["message"])
+
+ return js_response
+
+
+async def print_pdf(
+ tab: choreographer.Tab,
+) -> str:
+ pdf_params = {
+ "printBackground": True,
+ "marginTop": 0.1,
+ "marginBottom": 0.1,
+ "marginLeft": 0.1,
+ "marginRight": 0.1,
+ "preferCSSPageSize": True,
+ "pageRanges": "1",
+ }
+ pdf_response = await tab.send_command(
+ "Page.printToPDF",
+ params=pdf_params,
+ )
+ _raise_error(pdf_response)
+ return pdf_response.get("result", {}).get("data") # Check for None?
diff --git a/src/py/kaleido/_kaleido_tab/_errors.py b/src/py/kaleido/_kaleido_tab/_errors.py
new file mode 100644
index 00000000..f9a088b1
--- /dev/null
+++ b/src/py/kaleido/_kaleido_tab/_errors.py
@@ -0,0 +1,46 @@
+from choreographer.errors import DevtoolsProtocolError
+
+
+class JavascriptError(RuntimeError):
+ """Used to report errors from javascript."""
+
+
+### Error definitions ###
+class KaleidoError(Exception):
+ """
+ An error to interpret errors from Kaleido's JS side.
+
+ This is not for all js errors, just kaleido_scopes.js errors.
+ """
+
+ def __init__(self, code, message):
+ """
+ Construct an error object.
+
+ Args:
+ code: the number code of the error.
+ message: the message of the error.
+
+ """
+ super().__init__(message)
+ self._code = code
+ self._message = message
+
+ def __str__(self):
+ """Display the KaleidoError nicely."""
+ return f"Error {self._code}: {self._message}"
+
+
+def _get_error(result):
+ """Check browser response for errors. Helper function."""
+ if "error" in result:
+ return DevtoolsProtocolError(result)
+ if result.get("result", {}).get("result", {}).get("subtype", None) == "error":
+ return JavascriptError(str(result.get("result")))
+ return None
+
+
+def _raise_error(result):
+ e = _get_error(result)
+ if e:
+ raise e
diff --git a/src/py/kaleido/_kaleido_tab/_js_logger.py b/src/py/kaleido/_kaleido_tab/_js_logger.py
new file mode 100644
index 00000000..bad0516e
--- /dev/null
+++ b/src/py/kaleido/_kaleido_tab/_js_logger.py
@@ -0,0 +1,46 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+import logistro
+
+_logger = logistro.getLogger(__name__)
+
+if TYPE_CHECKING:
+ from typing import Any
+
+ import choreographer
+
+
+def _make_console_logger(name, log):
+ """Create printer specifically for console events. Helper function."""
+
+ async def console_printer(event):
+ _logger.debug2(f"{name}:{event}")
+ log.append(str(event))
+
+ return console_printer
+
+
+class JavascriptLogger:
+ log: list[Any]
+ """A list of console outputs from the tab."""
+
+ def __init__(self, tab: choreographer.Tab) -> None:
+ self.log = []
+ self.tab = tab
+
+ def activate(self):
+ self.tab.unsubscribe("Runtime.consoleAPICalled")
+ self.tab.subscribe(
+ "Runtime.consoleAPICalled",
+ _make_console_logger("tab js console", self.log),
+ )
+
+ def reset(self):
+ self.tab.unsubscribe("Runtime.consoleAPICalled")
+ self.log = []
+ self.tab.subscribe(
+ "Runtime.consoleAPICalled",
+ _make_console_logger("tab js console", self.log),
+ )
diff --git a/src/py/kaleido/_kaleido_tab/_tab.py b/src/py/kaleido/_kaleido_tab/_tab.py
new file mode 100644
index 00000000..2c1da8df
--- /dev/null
+++ b/src/py/kaleido/_kaleido_tab/_tab.py
@@ -0,0 +1,156 @@
+from __future__ import annotations
+
+import base64
+from typing import TYPE_CHECKING
+
+import logistro
+
+from . import _devtools_utils as _dtools
+from . import _js_logger
+from ._errors import _raise_error
+
+if TYPE_CHECKING:
+ import asyncio
+ from pathlib import Path
+
+ import choreographer as choreo
+
+ from kaleido._utils import fig_tools
+
+
+_TEXT_FORMATS = ("svg", "json") # eps
+
+_logger = logistro.getLogger(__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)
+ while new_future.done():
+ _logger.debug2(f"Clearing an old {event}")
+ new_future = tab.subscribe_once(event)
+ return new_future
+
+
+class _KaleidoTab:
+ """
+ A Kaleido tab is a wrapped choreographer tab providing the functions we need.
+
+ The choreographer tab can be accessed through the `self.tab` attribute.
+ """
+
+ tab: choreo.Tab
+ """The underlying choreographer tab."""
+ js_logger: _js_logger.JavascriptLogger
+ """A log for recording javascript."""
+
+ def __init__(self, tab):
+ """
+ Create a new _KaleidoTab.
+
+ Args:
+ tab: the choreographer tab to wrap.
+
+ """
+ self.tab = tab
+ self.js_logger = _js_logger.JavascriptLogger(self.tab)
+
+ async def navigate(self, url: str | Path = ""):
+ """
+ Navigate to the kaleidofier script. This is effectively the real initialization.
+
+ Args:
+ url: Override the location of the kaleidofier script if necessary.
+
+ """
+ # Subscribe to event which will contain javascript engine ID (need it
+ # for calling javascript functions)
+ javascript_ready = _subscribe_new(self.tab, "Runtime.executionContextCreated")
+
+ # Subscribe to event indicating page ready.
+ page_ready = _subscribe_new(self.tab, "Page.loadEventFired")
+
+ # Navigating page. This will trigger the above events.
+ _logger.debug2(f"Calling Page.navigate on {self.tab}")
+ _raise_error(await self.tab.send_command("Page.navigate", params={"url": url}))
+
+ # Enabling page events (for page_ready- like all events, if already
+ # ready, the latest will fire immediately)
+ _logger.debug2(f"Calling Page.enable on {self.tab}")
+ _raise_error(await self.tab.send_command("Page.enable"))
+
+ # Enabling javascript events (for javascript_ready)
+ _logger.debug2(f"Calling Runtime.enable on {self.tab}")
+ _raise_error(await self.tab.send_command("Runtime.enable"))
+
+ self._current_js_id = _dtools.get_js_id(await javascript_ready)
+
+ await page_ready # don't care result, ready is ready
+
+ # this runs *after* page load because running it first thing
+ # requires a couple extra lines
+ self.js_logger.reset()
+
+ # reload is truly so close to navigate
+ async def reload(self):
+ """Reload the tab, and set the javascript runtime id."""
+ _logger.debug(f"Reloading tab {self.tab} with javascript.")
+
+ javascript_ready = _subscribe_new(self.tab, "Runtime.executionContextCreated")
+
+ page_ready = _subscribe_new(self.tab, "Page.loadEventFired")
+
+ _logger.debug2(f"Calling Page.reload on {self.tab}")
+ _raise_error(await self.tab.send_command("Page.reload"))
+
+ self._current_js_id = _dtools.get_js_id(await javascript_ready)
+
+ await page_ready
+
+ self.js_logger.reset()
+
+ async def _calc_fig(
+ self,
+ spec: fig_tools.Spec,
+ *,
+ topojson: str | None,
+ render_prof,
+ stepper,
+ ) -> bytes:
+ # js script
+ 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")
+ 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")
+
+ _logger.debug2(f"Result of function call: {result}")
+ js_response = _dtools.check_kaleido_js_response(result)
+
+ if (response_format := js_response.get("format")) == "pdf":
+ render_prof.profile_log.tick("printing pdf")
+ img_raw = await _dtools.print_pdf(self.tab)
+ render_prof.profile_log.tick("pdf printed")
+ else:
+ img_raw = js_response["result"]
+
+ if response_format not in _TEXT_FORMATS:
+ res = base64.b64decode(img_raw)
+ else:
+ res = str.encode(img_raw)
+
+ render_prof.data_out_size = len(res)
+ render_prof.js_log = self.js_logger.log
+ return res
diff --git a/src/py/kaleido/_mocker.py b/src/py/kaleido/_mocker.py
deleted file mode 100644
index 6c9fe419..00000000
--- a/src/py/kaleido/_mocker.py
+++ /dev/null
@@ -1,301 +0,0 @@
-from __future__ import annotations
-
-import argparse
-import asyncio
-import multiprocessing
-import sys
-import time
-import warnings
-from pathlib import Path
-from pprint import pp
-from random import sample
-from typing import TypedDict
-
-import logistro
-import orjson
-
-import kaleido
-
-_logger = logistro.getLogger(__name__)
-
-cpus = multiprocessing.cpu_count()
-
-# Extract jsons of mocks
-test_dir = Path(__file__).resolve().parent.parent / "integration_tests"
-in_dir = test_dir / "mocks"
-out_dir = test_dir / "renders"
-
-
-def _get_jsons_in_paths(path: str | Path) -> list[Path]:
- # Work with Paths and directories
- path = Path(path) if isinstance(path, str) else path
-
- if path.is_dir():
- _logger.info(f"Input is path {path}")
- return list(path.glob("*.json"))
- elif path.is_file():
- _logger.info(f"Input is file {path}")
- return [path]
- else:
- raise TypeError("--input must be file or directory")
-
-
-class Param(TypedDict):
- name: str
- opts: dict[str, int | float]
-
-
-def _load_figures_from_paths(paths: list[Path]):
- # Set json
- params: list[Param]
- for path in paths:
- if path.is_file():
- with path.open(encoding="utf-8") as file:
- figure = orjson.loads(file.read())
- _logger.info(f"Yielding {path.stem}")
- if args.parameterize_opts is False:
- params = [
- {
- "name": f"{path.stem}.{args.format or 'png'}",
- "opts": {
- "scale": args.scale,
- "width": args.width,
- "height": args.height,
- },
- },
- ]
- else:
- widths = [args.width] if args.width else [200, 700, 1000]
- heights = [args.height] if args.height else [200, 500, 1000]
- scales = [args.scale] if args.scale else [0.5, 1, 2]
- formats = (
- [args.format]
- if args.format
- else [
- "png",
- "pdf",
- "jpg",
- "webp",
- "svg",
- "json",
- ]
- )
- params = []
- for w in widths:
- for h in heights:
- for s in scales:
- for f in formats:
- params.append(
- {
- "name": (
- f"{path.stem!s}-{w!s}"
- f"x{h!s}X{s!s}.{f!s}"
- ),
- "opts": {
- "scale": s,
- "width": w,
- "height": h,
- },
- },
- )
- for p in params:
- yield {
- "fig": figure,
- "path": str(Path(args.output) / p["name"]),
- "opts": p["opts"],
- }
- else:
- raise RuntimeError(f"Path {path} is not a file.")
-
-
-# Set the arguments
-description = """kaleido_mocker will load up json files of plotly figs and export them.
-
-If you set multiple process, -n, non-headless mode won't function well because
-chrome will actually throttle tabs or windows/visibile- unless that tab/window
-is headless.
-
-The export of the program is a json object containing information about the execution.
-"""
-
-if "--headless" in sys.argv and "--no-headless" in sys.argv:
- raise ValueError(
- "Choose either '--headless' or '--no-headless'.",
- )
-
-parser = argparse.ArgumentParser(
- add_help=True,
- parents=[logistro.parser],
- conflict_handler="resolve",
- description=description,
-)
-parser.add_argument(
- "--logistro-level",
- default="INFO",
- dest="log",
- help="Set the logging level (default INFO)",
-)
-parser.add_argument(
- "--n",
- type=int,
- default=cpus,
- help="Number of tabs, defaults to # of cpus",
-)
-parser.add_argument(
- "--input",
- type=str,
- default=in_dir,
- help="Directory of mock file/s or single file (default tests/mocks)",
-)
-parser.add_argument(
- "--output",
- type=str,
- default=out_dir,
- help="DIRECTORY of mock file/s (default tests/renders)",
-)
-parser.add_argument(
- "--format",
- type=str,
- default=None,
- help="png (default), pdf, jpg, webp, svg, json",
-)
-parser.add_argument(
- "--width",
- type=str,
- default=None,
- help="width in pixels (default 700)",
-)
-parser.add_argument(
- "--height",
- type=str,
- default=None,
- help="height in pixels (default 500)",
-)
-parser.add_argument(
- "--scale",
- type=str,
- default=None,
- help="Scale ratio, acts as multiplier for height/width (default 1)",
-)
-parser.add_argument(
- "--parameterize_opts",
- action="store_true",
- default=False,
- help="Run mocks w/ different configurations.",
-)
-parser.add_argument(
- "--timeout",
- type=int,
- default=90,
- help="Set timeout in seconds for any 1 mock (default 60 seconds)",
-)
-parser.add_argument(
- "--headless",
- action="store_true",
- default=True,
- help="Set headless as True (default)",
-)
-parser.add_argument(
- "--no-headless",
- action="store_false",
- dest="headless",
- help="Set headless as False",
-)
-parser.add_argument(
- "--stepper",
- action="store_true",
- default=False,
- dest="stepper",
- help="Stepper sets n to 1, headless to False, no timeout "
- "and asks for confirmation before printing.",
-)
-parser.add_argument(
- "--random",
- type=int,
- default=0,
- help="Will select N random jsons- or if 0 (default), all.",
-)
-parser.add_argument(
- "--fail-fast",
- action="store_true",
- default=False,
- help="Throw first error encountered and stop execution.",
-)
-
-args = parser.parse_args()
-logistro.getLogger().setLevel(args.log)
-
-if not Path(args.output).is_dir():
- raise ValueError(f"Specified output must be existing directory. Is {args.output!s}")
-
-
-# Function to process the images
-async def _main(error_log=None, profiler=None):
- paths = _get_jsons_in_paths(args.input)
- if args.random:
- if args.random > len(paths):
- raise ValueError(
- f"Input discover {len(paths)} paths, but a sampling of"
- f"{args.random} was asked for.",
- )
- paths = sample(paths, args.random)
- if args.stepper:
- _logger.info("Setting stepper.")
- args.n = 1
- args.headless = False
- args.timeout = 0
- if args.format == "svg":
- warnings.warn(
- "Stepper won't render svgs. It's feasible, "
- "but the adaption is just slightly more involved.",
- stacklevel=1,
- )
- await asyncio.sleep(3)
- # sets a global in kaleido, gross huh
-
- async with kaleido.Kaleido(
- page_generator=kaleido.PageGenerator(force_cdn=True),
- n=args.n,
- headless=args.headless,
- timeout=args.timeout,
- stepper=args.stepper,
- ) as k:
- await k.write_fig_from_object(
- _load_figures_from_paths(paths),
- error_log=error_log,
- profiler=profiler,
- )
-
-
-def build_mocks():
- start = time.perf_counter()
- try:
- error_log = [] if not args.fail_fast else None
- profiler = {}
- asyncio.run(_main(error_log, profiler))
- finally:
- # ruff: noqa: PLC0415
- from operator import itemgetter
-
- for tab, tab_profile in profiler.items():
- profiler[tab] = sorted(
- tab_profile,
- key=itemgetter("duration"),
- reverse=True,
- )
-
- elapsed = time.perf_counter() - start
- with_error_log = error_log is not None
- results = {
- "error_log": [str(log) for log in error_log] if with_error_log else None,
- "profiles": profiler,
- "total_time": f"Time taken: {elapsed:.6f} seconds",
- "total_errors": len(error_log) if with_error_log else "untracked",
- }
- pp(results)
- if error_log:
- sys.exit(1)
-
-
-if __name__ == "__main__":
- build_mocks()
diff --git a/src/py/kaleido/_page_generator.py b/src/py/kaleido/_page_generator.py
index 34b4064e..f99ad5a4 100644
--- a/src/py/kaleido/_page_generator.py
+++ b/src/py/kaleido/_page_generator.py
@@ -1,11 +1,20 @@
from __future__ import annotations
from pathlib import Path
-from urllib.parse import urlparse
-from urllib.request import url2pathname
+from typing import TYPE_CHECKING
import logistro
+from ._utils import path_tools
+
+if TYPE_CHECKING:
+ from typing import Tuple, Union
+
+ from typing_extensions import TypeAlias
+
+ UrlAndCharset: TypeAlias = Tuple[Union[str, Path], str]
+ """A tuple to explicitly set charset= in the '
script_tag_charset = '\n '
@@ -146,8 +147,4 @@ def generate_index(self, path=None):
page += script_tag_charset % script
page += self.footer
_logger.debug2(page)
- if not path:
- return page
- with (path).open("w") as f:
- f.write(page)
- return path.as_uri()
+ return page
diff --git a/src/py/kaleido/_profiler.py b/src/py/kaleido/_profiler.py
new file mode 100644
index 00000000..6ae29231
--- /dev/null
+++ b/src/py/kaleido/_profiler.py
@@ -0,0 +1,68 @@
+from __future__ import annotations
+
+import time
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from pathlib import Path
+ from typing import Any
+
+ from ._utils import fig_tools
+
+ Event = str
+
+
+class WriteCall:
+ name: str
+ renders: list[RenderTaskProfile]
+
+ __slots__ = tuple(__annotations__)
+
+ def __init__(self, name: str):
+ self.name = name
+ self.renders = []
+
+
+class RenderTaskProfile:
+ info: dict[str, Any] # literal?
+ error: None | BaseException
+ js_log: list[str]
+ profile_log: ProfileLog
+ data_in_size: int | None
+ data_out_size: int | None
+
+ __slots__ = tuple(__annotations__)
+
+ def __init__(
+ self,
+ spec: fig_tools.Spec,
+ full_path: Path | None,
+ tab_id: str,
+ ) -> None:
+ self.info = {}
+ self.error = None
+ self.js_log = []
+ self.profile_log = ProfileLog()
+ self.data_in_size = None # need to get this from choreographer
+ self.data_out_size = None
+
+ self.info.update(
+ {k: v for k, v in spec.items() if k != "data"},
+ )
+ self.info["path"] = full_path
+ self.info["tab"] = tab_id
+
+
+class ProfileLog:
+ _logs: dict[Event, float]
+
+ __slots__ = tuple(__annotations__)
+
+ def __init__(self) -> None:
+ self._logs = {}
+
+ def tick(self, name: str) -> None:
+ self._logs[name] = time.perf_counter()
+
+ def get_logs(self) -> dict[Event, float]:
+ return self._logs
diff --git a/src/py/kaleido/_sync_server.py b/src/py/kaleido/_sync_server.py
index 714fcce2..740bd902 100644
--- a/src/py/kaleido/_sync_server.py
+++ b/src/py/kaleido/_sync_server.py
@@ -11,7 +11,7 @@
from .kaleido import Kaleido
if TYPE_CHECKING:
- from typing import Any
+ from typing import Any, Callable
class Task(NamedTuple):
@@ -95,7 +95,20 @@ def close(self, *, silence_warnings=False):
del self._return_queue
self._initialized = False
- def call_function(self, cmd: str, *args, **kwargs):
+ def call_function(self, cmd: str, *args: Any, **kwargs: Any):
+ """
+ Call any function on the singleton Kaleido object.
+
+ Preferred functions would be: `calc_fig`, `write_fig`, and
+ `write_fig_from_object`. Methods that doesn't exist will raise a
+ BaseException.
+
+ Args:
+ cmd (str): the name of the method to call
+ args (Any): the method's arguments
+ kwargs (Any): the method's keyword arguments
+
+ """
if not self.is_running():
raise RuntimeError("Can't call function on stopped server.")
if kwargs.pop("kopts", None):
@@ -113,7 +126,24 @@ def call_function(self, cmd: str, *args, **kwargs):
return res
-def oneshot_async_run(func, args: tuple[Any, ...], kwargs: dict):
+def oneshot_async_run(
+ func: Callable,
+ args: tuple[Any, ...],
+ kwargs: dict[str, Any],
+) -> Any:
+ """
+ Run a thread to execute a single function.
+
+ Used by _sync functions in
+ `__init__` to ensure their async loop is separate from the users main
+ one.
+
+ Args:
+ func: the function to run
+ args: a tuple of arguments to pass
+ kwargs: a dictionary of keyword arguments to pass
+
+ """
q: Queue[Any] = Queue(maxsize=1)
def run(func, q, *args, **kwargs):
diff --git a/src/py/kaleido/_utils.py b/src/py/kaleido/_utils/__init__.py
similarity index 54%
rename from src/py/kaleido/_utils.py
rename to src/py/kaleido/_utils/__init__.py
index 052f79b9..c03c45c6 100644
--- a/src/py/kaleido/_utils.py
+++ b/src/py/kaleido/_utils/__init__.py
@@ -1,16 +1,66 @@
+from __future__ import annotations
+
import asyncio
-import traceback
import warnings
from functools import partial
from importlib.metadata import PackageNotFoundError, version
+from typing import TYPE_CHECKING
import logistro
from packaging.version import Version
_logger = logistro.getLogger(__name__)
+if TYPE_CHECKING:
+ from typing import Any, Callable, Coroutine
+
+
+def event_printer(name: str) -> Callable[[Any], Coroutine[Any, Any, None]]:
+ """Return function that prints whatever argument received."""
+
+ async def print_all(response: Any) -> None:
+ _logger.debug2(f"{name!s}:{response!s}")
+
+ return print_all
+
+
+def _clean_error(t: asyncio.Task) -> None:
+ """Check a task to avoid "task never awaited" errors."""
+ if t.cancelled():
+ _logger.error(f"{t} cancelled.")
+ elif (exc := t.exception()) is not None:
+ _logger.error(f"{t} raised error.", exc_info=exc)
+
+
+def create_task_log_error(coroutine) -> asyncio.Task:
+ """Create a task and assign a callback to log its errors."""
+ t = asyncio.create_task(coroutine)
+ t.add_done_callback(_clean_error)
+ return t
+
+
+def ensure_async_iter(obj):
+ """Convert any iterable to an async iterator."""
+ if hasattr(obj, "__aiter__"):
+ return obj
+
+ it = iter(obj)
+
+ class _AIter:
+ def __aiter__(self):
+ return self
+
+ async def __anext__(self):
+ try:
+ return next(it)
+ except StopIteration:
+ raise StopAsyncIteration # noqa: B904
+
+ return _AIter()
+
async def to_thread(func, *args, **kwargs):
+ """Polyfill `asyncio.to_thread()`."""
_loop = asyncio.get_running_loop()
fn = partial(func, *args, **kwargs)
await _loop.run_in_executor(None, fn)
@@ -35,7 +85,10 @@ def warn_incompatible_plotly():
"This means that static image generation (e.g. `fig.write_image()`) "
"will not work.\n\n"
f"Please upgrade Plotly to version {min_compatible_plotly_version} "
- "or greater, or downgrade Kaleido to version 0.2.1."
+ "or greater, or downgrade Kaleido to version 0.2.1.\n\n"
+ "You can however, use the Kaleido API directly which will work "
+ "with your plotly version. `kaleido.write_fig(...)`, for example. "
+ "Please see the kaleido documentation."
"\n",
UserWarning,
stacklevel=3,
@@ -49,30 +102,3 @@ def warn_incompatible_plotly():
# Since this compatibility check is just a convenience,
# we don't want to block the whole library if there's an issue
_logger.info("Error while checking Plotly version.", exc_info=e)
-
-
-class ErrorEntry:
- """A simple object to record errors and context."""
-
- def __init__(self, name, error, javascript_log):
- """
- Construct an error entry.
-
- Args:
- name: the name of the image with the error
- error: the error object (from class BaseException)
- javascript_log: an array of entries from the javascript console
-
- """
- self.name = name
- self.error = error
- self.javascript_log = javascript_log
-
- def __str__(self):
- """Display the error object in a concise way."""
- ret = f"{self.name}:\n"
- e = self.error
- ret += " ".join(traceback.format_exception(type(e), e, e.__traceback__))
- ret += " javascript Log:\n"
- ret += "\n ".join(self.javascript_log)
- return ret
diff --git a/src/py/kaleido/_utils/fig_tools.py b/src/py/kaleido/_utils/fig_tools.py
new file mode 100644
index 00000000..71541097
--- /dev/null
+++ b/src/py/kaleido/_utils/fig_tools.py
@@ -0,0 +1,141 @@
+"""
+Tools to help prepare data for plotly.js from kaleido.
+
+It 1. validates, 2. write defaults, 3. packages object.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Literal, TypedDict
+
+import logistro
+
+from . import path_tools
+
+if TYPE_CHECKING:
+ from pathlib import Path
+ from typing import Any
+
+ from typing_extensions import TypeGuard
+
+ Figurish = Any # Be nice to make it more specific, dictionary or something
+ FormatString = Literal["png", "jpg", "jpeg", "webp", "svg", "json", "pdf"]
+
+
+# Input of to_spec (user gives us this)
+class LayoutOpts(TypedDict, total=False):
+ format: FormatString | None
+ scale: int | float
+ height: int | float
+ width: int | float
+
+
+# Output of to_spec (we give kaleido_scopes.js this)
+# refactor note: this could easily be right before send
+class Spec(TypedDict):
+ format: FormatString
+ width: int | float
+ height: int | float
+ scale: int | float
+ data: Figurish
+
+
+_logger = logistro.getLogger(__name__)
+
+# constants
+DEFAULT_EXT = "png"
+DEFAULT_SCALE = 1
+DEFAULT_WIDTH = 700
+DEFAULT_HEIGHT = 500
+SUPPORTED_FORMATS: tuple[FormatString, ...] = (
+ "png",
+ "jpg",
+ "jpeg",
+ "webp",
+ "svg",
+ "json",
+ "pdf",
+)
+
+
+# validation function
+def is_figurish(o: Any) -> TypeGuard[Figurish]:
+ # so if data isn't in the dict things get weird.
+ valid = hasattr(o, "to_dict") or (isinstance(o, dict) and "data" in o)
+ if not valid:
+ _logger.debug(
+ f"Figure has to_dict? {hasattr(o, 'to_dict')} "
+ f"is dict? {isinstance(o, dict)} "
+ f"Keys: {o.keys() if hasattr(o, 'keys') else None!s}",
+ )
+ return valid
+
+
+def _coerce_format(extension: str) -> FormatString:
+ # wrap this condition as a typeguard for typechecker's sake
+ def is_fmt(s: str) -> TypeGuard[FormatString]:
+ return s in SUPPORTED_FORMATS
+
+ formatted_extension = extension.lower()
+ if formatted_extension == "jpg":
+ return "jpeg"
+ elif not is_fmt(formatted_extension):
+ raise ValueError(
+ f"Invalid format '{formatted_extension}'.\n"
+ f"Supported formats: {SUPPORTED_FORMATS!s}",
+ )
+ else:
+ return formatted_extension
+
+
+def coerce_for_js(
+ fig: Figurish,
+ path: Path | str | None,
+ opts: LayoutOpts | None,
+) -> Spec:
+ if not is_figurish(fig): # VALIDATE FIG
+ raise TypeError("Figure supplied doesn't seem to be a valid plotly figure.")
+ if hasattr(fig, "to_dict"): # COERCE FIG
+ fig = fig.to_dict()
+
+ path = path_tools.get_path(path) if path else None
+
+ opts = opts or {}
+
+ if _rest := opts - LayoutOpts.__annotations__.keys():
+ raise AttributeError(f"Unknown key(s) in layout options: {_rest}")
+
+ # Extract info
+ file_format = _coerce_format(
+ opts.get("format")
+ or (path.suffix.lstrip(".") if path and path.suffix else DEFAULT_EXT),
+ )
+
+ layout = fig.get("layout", {})
+
+ width = (
+ opts.get("width")
+ or layout.get("width")
+ or layout.get("template", {}).get("layout", {}).get("width")
+ or DEFAULT_WIDTH
+ )
+
+ height = (
+ opts.get("height")
+ or layout.get("height")
+ or layout.get("template", {}).get("layout", {}).get("height")
+ or DEFAULT_HEIGHT
+ )
+
+ scale = opts.get("scale", DEFAULT_SCALE)
+
+ # PACKAGING
+ spec: Spec = {
+ "format": file_format,
+ "width": width,
+ "height": height,
+ "scale": scale,
+ "data": fig,
+ }
+
+ return spec
diff --git a/src/py/kaleido/_utils/path_tools.py b/src/py/kaleido/_utils/path_tools.py
new file mode 100644
index 00000000..fae5cf19
--- /dev/null
+++ b/src/py/kaleido/_utils/path_tools.py
@@ -0,0 +1,77 @@
+from __future__ import annotations
+
+import glob
+import re
+from pathlib import Path
+from typing import TYPE_CHECKING
+from urllib.parse import urlparse
+from urllib.request import url2pathname
+
+import logistro
+
+if TYPE_CHECKING:
+ from . import fig_tools
+
+_logger = logistro.getLogger(__name__)
+
+
+def _next_filename(path: Path | str, prefix: str, ext: str) -> str:
+ path = path if isinstance(path, Path) else Path(path)
+ default = 1 if (path / f"{prefix}.{ext}").exists() else 0
+ re_number = re.compile(
+ r"^" + re.escape(prefix) + r"\-(\d+)\." + re.escape(ext) + r"$",
+ )
+ escaped_prefix = glob.escape(prefix)
+ escaped_ext = glob.escape(ext)
+ numbers = [
+ int(match.group(1))
+ for name in path.glob(f"{escaped_prefix}-*.{escaped_ext}")
+ if (match := re_number.match(Path(name).name))
+ ]
+ n = max(numbers, default=default) + 1
+ return f"{prefix}.{ext}" if n == 1 else f"{prefix}-{n}.{ext}"
+
+
+def determine_path(
+ path: Path | str | None,
+ fig: dict,
+ ext: fig_tools.FormatString,
+) -> Path:
+ path = Path(path) if path else Path()
+
+ if not path.suffix or path.is_dir(): # they gave us a directory
+ if not path.is_dir():
+ raise ValueError(f"Directory {path} not found. Please create it.")
+ directory = path
+ _logger.debug("Looking for title")
+ prefix = fig.get("layout", {}).get("title", {}).get("text", "fig")
+ prefix = re.sub(r"[ \-]", "_", prefix)
+ prefix = re.sub(r"[^a-zA-Z0-9_]", "", prefix)
+ prefix = prefix or "fig"
+ _logger.debug(f"Found: {prefix}")
+ name = _next_filename(directory, prefix, ext)
+ full_path = directory / name
+ else: # we have full path, supposedly
+ full_path = path
+ if not full_path.parent.is_dir():
+ raise RuntimeError(
+ f"Cannot reach path {path.parent}. Are all directories created?",
+ )
+ return full_path
+
+
+def get_path(p: str | Path) -> Path:
+ if isinstance(p, Path):
+ return p
+ elif not isinstance(p, str):
+ raise TypeError("Path should be a string or `pathlib.Path` object.")
+
+ parsed = urlparse(str(p))
+
+ return Path(
+ url2pathname(parsed.path) if parsed.scheme.startswith("file") else p,
+ )
+
+
+def is_httpish(p: str) -> bool:
+ return urlparse(str(p)).scheme.startswith("http")
diff --git a/src/py/kaleido/kaleido.py b/src/py/kaleido/kaleido.py
index 6ee843a0..43c75f2a 100644
--- a/src/py/kaleido/kaleido.py
+++ b/src/py/kaleido/kaleido.py
@@ -3,33 +3,64 @@
from __future__ import annotations
import asyncio
-import warnings
+from collections import deque
from collections.abc import AsyncIterable, Iterable
-from functools import partial
from pathlib import Path
-from typing import TYPE_CHECKING
-from urllib.parse import unquote, urlparse
+from typing import TYPE_CHECKING, TypedDict, cast, overload
import choreographer as choreo
import logistro
from choreographer.errors import ChromeNotFoundError
from choreographer.utils import TmpDirectory
-from ._fig_tools import _is_figurish, build_fig_spec
+from . import _profiler, _utils
from ._kaleido_tab import _KaleidoTab
from ._page_generator import PageGenerator
-from ._utils import ErrorEntry, warn_incompatible_plotly
+from ._utils import fig_tools, path_tools
if TYPE_CHECKING:
from types import TracebackType
- from typing import Any, Callable, Coroutine
+ from typing import (
+ Any,
+ AsyncGenerator,
+ List,
+ Literal,
+ Tuple,
+ TypeVar,
+ Union,
+ ValuesView,
+ )
+
+ from typing_extensions import NotRequired, Required, TypeAlias, TypeGuard
+
+ T = TypeVar("T")
+ AnyIterable: TypeAlias = Union[Iterable[T], AsyncIterable[T]] # not runtime
+
+ # union of sized iterables since 3.8 doesn't have & operator
+ # Iterable & Sized
+ Listish: TypeAlias = Union[Tuple[T], List[T], ValuesView[T]]
+
+ class FigureDict(TypedDict):
+ """The type a fig_dicts returns for `write_fig_from_object`."""
+
+ fig: Required[fig_tools.Figurish]
+ path: NotRequired[None | str | Path]
+ opts: NotRequired[fig_tools.LayoutOpts | None]
+ topojson: NotRequired[None | str]
+
+
+def _is_figuredict(obj: Any) -> TypeGuard[FigureDict]:
+ return isinstance(obj, dict) and "fig" in obj
- from . import _fig_tools
_logger = logistro.getLogger(__name__)
+# Show a warning if the installed Plotly version
+# is incompatible with this version of Kaleido
+_utils.warn_incompatible_plotly()
+
try:
- from plotly.utils import PlotlyJSONEncoder # noqa: I001
+ from plotly.utils import PlotlyJSONEncoder # type: ignore[import-untyped] # noqa: I001
from choreographer import channels
channels.register_custom_encoder(PlotlyJSONEncoder)
@@ -37,239 +68,228 @@
except ImportError as e:
_logger.debug(f'Couldn\'t import plotly due to "{e!s}" - skipping.')
-# Show a warning if the installed Plotly version
-# is incompatible with this version of Kaleido
-warn_incompatible_plotly()
+class Kaleido(choreo.Browser):
+ """
+ The Kaleido object provides a browser to render and write plotly figures.
-def _make_printer(name: str) -> Callable[[Any], Coroutine[Any, Any, None]]:
- """Create event printer for generic events. Helper function."""
-
- async def print_all(response: Any) -> None:
- _logger.debug2(f"{name}:{response}")
+ It provides methods to render said figures, and manages any number of tabs
+ in a work queue. Start it one of a few equal ways:
- return print_all
+ async with Kaleido() as k:
+ ...
+ # or
-class Kaleido(choreo.Browser):
- """
- Kaleido manages a set of image processors.
+ k = await Kaleido()
+ ...
+ await k.close()
- It can be used as a context (`async with Kaleido(...)`), but can
- also be used like:
+ # or
- ```
- k = Kaleido(...)
- k = await Kaleido.open()
- ... # do stuff
- k.close()
- ```
+ k = Kaleido()
+ await k.open()
+ ...
+ await.k.close()
"""
tabs_ready: asyncio.Queue[_KaleidoTab]
- """A queue of ready tabs."""
- _background_render_tasks: set[asyncio.Task]
- # not really render tasks
- _main_tasks: set[asyncio.Task]
+ """A queue of tabs ready to process a kaleido figure."""
+ _main_render_coroutines: set[asyncio.Task]
+ # technically Tasks, user sees coroutines
+ profiler: deque[_profiler.WriteCall]
- async def close(self) -> None:
- """Close the browser."""
- await super().close()
- if self._tmp_dir:
- self._tmp_dir.clean()
- _logger.info("Cancelling tasks.")
- for task in self._main_tasks:
- if not task.done():
- task.cancel()
- for task in self._background_render_tasks:
- if not task.done():
- task.cancel()
- _logger.info("Exiting Kaleido/Choreo")
+ _total_tabs: int
+ _html_tmp_dir: None | TmpDirectory
- async def __aexit__(
- self,
- exc_type: type[BaseException] | None,
- exc_value: BaseException | None,
- exc_tb: TracebackType | None,
- ) -> bool | None:
- """Close the browser."""
- _logger.info("Waiting for all cleanups to finish.")
- await asyncio.gather(*self._background_render_tasks, return_exceptions=True)
- _logger.info("Exiting Kaleido")
- return await super().__aexit__(exc_type, exc_value, exc_tb)
+ ### KALEIDO LIFECYCLE FUNCTIONS ###
- def __init__( # noqa: D417, PLR0913 no args/kwargs in description
+ def __init__(
self,
- *args: Any,
- page_generator: None | PageGenerator | str | Path = None,
+ # *args: Any, force named vars for all choreographer passthrough
n: int = 1,
timeout: int | None = 90,
- width: int | None = None, # deprecate
- height: int | None = None, # deprecate
- stepper: bool = False,
- plotlyjs: str | None = None,
- mathjax: str | None = None,
+ page_generator: None | PageGenerator | str | Path = None,
+ plotlyjs: str | Path | None = None,
+ mathjax: str | Path | Literal[False] | None = None,
**kwargs: Any,
) -> None:
"""
- Initialize Kaleido, a `choreo.Browser` wrapper adding kaleido functionality.
-
- It takes all `choreo.Browser` args, plus some extra. The extra
- are listed, see choreographer for more documentation.
-
- Note: Chrome will throttle background tabs and windows, so non-headless
- multi-process configurations don't work well.
-
- For argument `page`, if it is a string, it must be passed as a fully-qualified
- URI, like `file://` or `https://`.
- If it is a `Path`, `Path`'s `as_uri()` will be called.
- If it is a string or path, its expected to be an HTML file, one will not
- be generated.
+ Create a new Kaleido process for rendering plotly figures.
Args:
- n: the number of separate processes (windows, not seen) to use.
- timeout: limit on any single render (default 90 seconds).
- width: width of window (headless only)
- height: height of window (headless only)
- page: This can be a `kaleido.PageGenerator`, a `pathlib.Path`, or a string.
+ *args (Any):
+ Passed through to underlying choreographer.Browser()
+ n (int, optional):
+ Number of processors to use (parallelization). Defaults to 1.
+
+ timeout (int | None, optional):
+ Number of seconds to wait to render any one image. None for no
+ timeout. Defaults to 90.
+
+ page_generator (None | PageGenerator | str | Path, optional):
+ A PageGenerator object can be used for deep customization of the
+ plotly template page. This is for development use. You can also
+ pass a string or path directly to an index.html, or any object
+ with a `generate_index()->str function that prints an HTML
+ ppage. Defaults to None.
+
+ plotlyjs (str | Path | None, optional):
+ A path or URL to a plotly.js file. Defaults to None- which means
+ to use the plotly.js included with your version of plotly.py or
+ if not installed, the latest version available via CDN.
+
+ mathjax (str | Path | Literal[False] | None, optional):
+ A path or URL to a mathjax.js file. If Dalse, mathjax is
+ disabled. Defaults to None- which means to use version 2.35 via
+ CDN.
+
+ **kwargs (Any):
+ Additional keyword arguments passed through to the underlying
+ Choreographer.browser constructor. Notable options include
+ `headless=False` (show window), `enable_sandbox=True` (turn on
+ sandboxing), and `enable_gpu=True` which will allow use of the
+ GPU. The defaults for these options are True, False, and False
+ respectively.
"""
- self._background_render_tasks = set()
- self._main_tasks = set()
+ # State variables
+ self._main_render_coroutines = set()
self.tabs_ready = asyncio.Queue(maxsize=0)
- self._total_tabs = 0
- self._tmp_dir = None
+ self._total_tabs = 0 # tabs properly registered
+ self._html_tmp_dir = None
+ self.profiler: deque[_profiler.WriteCall] = deque(maxlen=5)
+
+ # Kaleido Config
+ if page_generator and (plotlyjs is not None or mathjax is not None):
+ raise ValueError(
+ "page_generator cannot be set with mathjax or plotlyjs",
+ )
page = page_generator
self._timeout = timeout
self._n = n
- self._height = height # deprecate
- self._width = width # deprecate
- self._stepper = stepper
self._plotlyjs = plotlyjs
self._mathjax = mathjax
- if not kwargs.get("headless", True) and (self._height or self._width):
- warnings.warn(
- "Height and Width can only be used if headless=True, "
- "ignoring both sizes.",
- stacklevel=1,
- )
- self._height = None
- self._width = None
+
+ # Diagnostic
_logger.debug(f"Timeout: {self._timeout}")
try:
- super().__init__(*args, **kwargs)
+ super().__init__(**kwargs)
except ChromeNotFoundError:
raise ChromeNotFoundError(
"Kaleido v1 and later requires Chrome to be installed. "
"To install Chrome, use the CLI command `kaleido_get_chrome`, "
- "or from Python, use either `kaleido.get_chrome()` "
+ "or from Python, use either `await kaleido.get_chrome()` "
"or `kaleido.get_chrome_sync()`.",
- ) from ChromeNotFoundError
+ ) from None # overwriting the error entirely. (diagnostics)
- # do this during open because it requires close
+ # save this for open() because it requires close()
self._saved_page_arg = page
async def open(self):
- """Build temporary file if we need one."""
+ """Build page and temporary file if we need one, then opens browser."""
page = self._saved_page_arg
del self._saved_page_arg
- if isinstance(page, str):
- if page.startswith(r"file://") and Path(unquote(urlparse(page).path)):
- self._index = page
- elif Path(page).is_file():
- self._index = Path(page).as_uri()
- else:
- raise FileNotFoundError(f"{page} does not exist.")
- elif isinstance(page, Path):
- if page.is_file():
- self._index = page.as_uri()
+ if isinstance(page, (Path, str)):
+ if (_p := _utils.get_path(page)).is_file():
+ self._index = _p.as_uri()
else:
raise FileNotFoundError(f"{page!s} does not exist.")
- else:
- self._tmp_dir = TmpDirectory(sneak=self.is_isolated())
- index = self._tmp_dir.path / "index.html"
+ elif not page or hasattr(page, "generate_index"):
+ self._html_tmp_dir = TmpDirectory(sneak=self.is_isolated())
+ index = self._html_tmp_dir.path / "index.html"
self._index = index.as_uri()
if not page:
page = PageGenerator(plotly=self._plotlyjs, mathjax=self._mathjax)
- page.generate_index(index)
+ with index.open("w") as f: # is blocking but ok
+ f.write(page.generate_index())
+ else:
+ raise TypeError(
+ "page_generator must be one of: None, a"
+ " PageGenerator, or a file path to an index.html.",
+ )
await super().open()
- async def _conform_tabs(self, tabs: list[choreo.Tab] | None = None) -> None:
+ async def _create_kaleido_tab(self) -> None:
+ tab = await super().create_tab(
+ url="",
+ window=True,
+ )
+ await self._conform_tabs([tab])
+
+ async def _conform_tabs(self, tabs: Listish[choreo.Tab] | None = None) -> None:
if not tabs:
- tabs = list(self.tabs.values())
+ tabs = self.tabs.values()
_logger.info(f"Conforming {len(tabs)} to {self._index}")
-
for i, tab in enumerate(tabs):
- n = f"tab-{i!s}"
_logger.debug2(f"Subscribing * to tab: {tab}.")
- tab.subscribe("*", _make_printer(n + " event"))
-
- _logger.debug("Navigating all tabs")
+ tab.subscribe("*", _utils.event_printer(f"tab-{i!s}: Event Dump:"))
- kaleido_tabs = [_KaleidoTab(tab, _stepper=self._stepper) for tab in tabs]
+ kaleido_tabs = [_KaleidoTab(tab) for tab in tabs]
- # A little hard to read because we don't have TaskGroup in this version
- tasks = [asyncio.create_task(tab.navigate(self._index)) for tab in kaleido_tabs]
- _logger.info("Waiting on all navigates")
- await asyncio.gather(*tasks)
- _logger.info("All navigates done, putting them all in queue.")
+ await asyncio.gather(*(tab.navigate(self._index) for tab in kaleido_tabs))
for ktab in kaleido_tabs:
+ self._total_tabs += 1
await self.tabs_ready.put(ktab)
- self._total_tabs = len(kaleido_tabs)
- _logger.debug("Tabs fully navigated/enabled/ready")
async def populate_targets(self) -> None:
"""
Override the browser populate_targets to ensure the correct page.
Is called automatically during initialization, and should only be called
- once ever per object.
+ once.
"""
await super().populate_targets()
await self._conform_tabs()
needed_tabs = self._n - len(self.tabs)
- if needed_tabs < 0:
- raise RuntimeError("Did you set 0 or less tabs?")
if not needed_tabs:
return
- tasks = [
- asyncio.create_task(self._create_kaleido_tab()) for _ in range(needed_tabs)
- ]
- await asyncio.gather(*tasks)
- for tab in self.tabs.values():
- _logger.info(f"Tab ready: {tab.target_id}")
+ await asyncio.gather(
+ *(self._create_kaleido_tab() for _ in range(needed_tabs)),
+ )
- async def _create_kaleido_tab(
+ async def close(self) -> None:
+ """Close the browser."""
+ if self._html_tmp_dir:
+ _logger.debug(f"Cleaning up {self._html_tmp_dir}")
+ self._html_tmp_dir.clean()
+ else:
+ _logger.debug("No kaleido._html_tmp_dir to clean up.")
+
+ await super().close()
+
+ # cancellation only happens if crash/early
+ _logger.info("Cancelling tasks.")
+ for task in self._main_render_coroutines:
+ if not task.done():
+ task.cancel()
+
+ _logger.info("Exiting Kaleido/Choreo.")
+
+ async def __aexit__(
self,
- ) -> None:
- """
- Create a tab with the kaleido script.
+ exc_type: type[BaseException] | None,
+ exc_value: BaseException | None,
+ exc_tb: TracebackType | None,
+ ) -> bool | None:
+ """Close the browser."""
+ _logger.info("Waiting for all cleanups to finish.")
- Returns:
- The kaleido-tab created.
+ # render "tasks" are coroutines, so use is awaiting them
- """
- tab = await super().create_tab(
- url="",
- width=self._width,
- height=self._height,
- window=True,
- )
- await self._conform_tabs([tab])
+ await asyncio.gather(*self._main_render_coroutines, return_exceptions=True)
- async def _get_kaleido_tab(self) -> _KaleidoTab:
- """
- Retrieve an available tab from queue.
+ _logger.info("Exiting Kaleido.")
+ return await super().__aexit__(exc_type, exc_value, exc_tb)
- Returns:
- A kaleido-tab from the queue.
+ ### TAB MANAGEMENT FUNCTIONS ####
- """
+ async def _get_kaleido_tab(self) -> _KaleidoTab:
_logger.info(f"Getting tab from queue (has {self.tabs_ready.qsize()})")
if not self._total_tabs:
raise RuntimeError(
@@ -280,13 +300,6 @@ async def _get_kaleido_tab(self) -> _KaleidoTab:
return tab
async def _return_kaleido_tab(self, tab: _KaleidoTab) -> None:
- """
- Refresh tab and put it back into the available queue.
-
- Args:
- tab: the kaleido tab to return.
-
- """
_logger.info(f"Reloading tab {tab.tab.target_id[:4]} before return.")
await tab.reload()
_logger.info(
@@ -296,316 +309,216 @@ async def _return_kaleido_tab(self, tab: _KaleidoTab) -> None:
await self.tabs_ready.put(tab)
_logger.debug(f"{tab.tab.target_id[:4]} put back.")
- def _clean_tab_return_task(
- self,
- main_task: asyncio.Task,
- task: asyncio.Task,
- ) -> None:
- _logger.info("Cleaning out background tasks.")
- self._background_render_tasks.remove(task)
- e = task.exception()
- if e:
- _logger.error("Clean tab return task found exception", exc_info=e)
- if not main_task.done():
- main_task.cancel()
- raise e
-
- def _check_render_task(
- self,
- name: str,
- tab: _KaleidoTab,
- main_task: asyncio.Task,
- error_log: None | list[ErrorEntry],
- task: asyncio.Task,
- ) -> None:
- if task.cancelled():
- _logger.info(f"Something cancelled {name}.")
- if error_log:
- error_log.append(
- ErrorEntry(name, asyncio.CancelledError, tab.javascript_log),
- )
- elif e := task.exception():
- _logger.error(f"Render Task Error In {name}- ", exc_info=e)
- if isinstance(e, (asyncio.TimeoutError, TimeoutError)) and error_log:
- error_log.append(
- ErrorEntry(name, e, tab.javascript_log),
- )
- else:
- _logger.error("Cancelling all.")
- if not main_task.done():
- main_task.cancel()
- raise e
- _logger.info(f"Returning {name} tab after render.")
- t = asyncio.create_task(self._return_kaleido_tab(tab))
- self._background_render_tasks.add(t)
- t.add_done_callback(partial(self._clean_tab_return_task, main_task))
-
+ # _retuner_task MUST calculate full_path before it awaits
async def _render_task(
self,
- tab: _KaleidoTab,
- args: Any,
- error_log: None | list[ErrorEntry] = None,
- profiler: None | list = None,
- ):
- _logger.info(f"Posting a task for {args['full_path'].name}")
- if self._timeout:
- try:
- await asyncio.wait_for(
- tab._write_fig( # noqa: SLF001 I don't want it documented, too complex for user
- **args,
- error_log=error_log,
- profiler=profiler,
- ),
- self._timeout, # timeout can be None, no need for branches
- )
- except BaseException as e:
- if error_log:
- error_log.append(
- ErrorEntry(
- args["full_path"].name,
- e,
- tab.javascript_log
- if hasattr(
- tab,
- "javascript_log",
- )
- else [],
- ),
- )
- else:
- raise
-
- else:
- await tab._write_fig( # noqa: SLF001 I don't want it documented, too complex for user
- **args,
- error_log=error_log,
- profiler=profiler,
- )
- _logger.info(f"Posted task ending for {args['full_path'].name}")
-
- async def calc_fig(
- self,
- fig: _fig_tools.Figurish,
- path: str | Path | None = None,
- opts: None | _fig_tools.LayoutOpts = None,
+ fig_arg: FigureDict,
*,
- topojson: str | None = None,
- ):
- """
- Calculate the bytes for a figure.
-
- This function does not support parallelism or multi-image processing like
- `write_fig` does, although its arguments are a subset of those of `write_fig`.
- This function is currently just meant to bridge the old and new API.
- """
- if not _is_figurish(fig) and isinstance(fig, Iterable):
- raise TypeError("Calc fig can not process multiple images at a time.")
- spec, full_path = build_fig_spec(fig, path, opts)
- tab = await self._get_kaleido_tab()
- args = {
- "spec": spec,
- "full_path": full_path,
- "topojson": topojson,
- }
- data = None
- timeout = self._timeout if self._timeout else None
- data = await asyncio.wait_for(
- tab._calc_fig( # noqa: SLF001 I don't want it documented, too complex for user
- **args,
- ),
- timeout,
+ topojson: str | None,
+ _write: bool,
+ profiler: _profiler.WriteCall,
+ stepper: bool,
+ ) -> None | bytes:
+ spec = fig_tools.coerce_for_js(
+ fig_arg.get("fig"),
+ fig_arg.get("path", None),
+ fig_arg.get("opts", None),
)
- await self._return_kaleido_tab(tab)
- return data[0]
- async def write_fig( # noqa: PLR0913, PLR0912, C901 (too many args, complexity)
- self,
- fig: _fig_tools.Figurish,
- path: str | Path | None = None,
- opts: _fig_tools.LayoutOpts | None = None,
- *,
- topojson: str | None = None,
- error_log: None | list[ErrorEntry] = None,
- profiler: None | list = None,
- ):
- """
- Call the plotly renderer via javascript on first available tab.
-
- Args:
- fig: the plotly figure or an iterable of plotly figures
- path: the path to write the images to. if its a directory, we will try to
- generate a name. If the path contains an extension,
- "path/to/my_image.png", that extension will be the format used if not
- overridden in `opts`. If you pass a complete path (filename), for
- multiple figures, you will overwrite every previous figure.
- opts: dictionary describing format, width, height, and scale of image
- topojson: a link ??? TODO
- error_log: a supplied list, will be populated with `ErrorEntry`s
- which can be converted to strings. Note, this is for
- collections errors that have to do with plotly. They will
- not be thrown. Lower level errors (kaleido, choreographer)
- will still be thrown. If not passed, all errors raise.
- profiler: a supplied dictionary to collect stats about the operation
- about tabs, runtimes, etc.
+ if _write:
+ full_path = path_tools.determine_path(
+ fig_arg.get("path", None),
+ spec["data"],
+ spec["format"], # should just take spec
+ )
+ full_path.touch() # claim our name
- """
- if error_log is not None:
- _logger.info("Using error log.")
- if profiler is not None:
- _logger.info("Using profiler.")
+ tab = await self._get_kaleido_tab()
- if _is_figurish(fig) or not isinstance(fig, Iterable):
- fig = [fig]
- else:
- _logger.debug(f"Is iterable {type(fig)}")
+ render_prof = _profiler.RenderTaskProfile(
+ spec,
+ full_path if _write else None,
+ tab.tab.target_id,
+ )
+ render_prof.profile_log.tick("acquired tab")
+ profiler.renders.append(render_prof)
- if main_task := asyncio.current_task():
- self._main_tasks.add(main_task)
- tasks = set()
-
- async def _loop(f):
- spec, full_path = build_fig_spec(f, path, opts)
- tab = await self._get_kaleido_tab()
- if profiler is not None and tab.tab.target_id not in profiler:
- profiler[tab.tab.target_id] = []
- t = asyncio.create_task(
- self._render_task(
- tab,
- args={
- "spec": spec,
- "full_path": full_path,
- "topojson": topojson,
- },
- error_log=error_log,
- profiler=profiler,
- ),
- )
- t.add_done_callback(
- partial(
- self._check_render_task,
- full_path.name,
- tab,
- main_task,
- error_log,
+ try:
+ img_bytes = await asyncio.wait_for(
+ tab._calc_fig( # noqa: SLF001
+ spec,
+ topojson=topojson,
+ render_prof=render_prof,
+ stepper=stepper,
),
+ self._timeout,
)
- tasks.add(t)
-
- try:
- if hasattr(fig, "__aiter__"): # is async iterable
- _logger.debug("Is async for")
- async for f in fig:
- await _loop(f)
+ if _write:
+ render_prof.profile_log.tick("starting file write")
+ await _utils.to_thread(full_path.write_bytes, img_bytes)
+ render_prof.profile_log.tick("file write done")
+ return None
else:
- _logger.debug("Is sync for")
- for f in fig:
- await _loop(f)
- _logger.debug("awaiting tasks")
- await asyncio.gather(*tasks, return_exceptions=True)
- except:
- _logger.exception("Cleaning tasks after error.")
- for task in tasks:
- if not task.done():
- task.cancel()
+ return img_bytes
+ except BaseException as e:
+ render_prof.profile_log.tick("errored out")
+ if _write:
+ full_path.unlink() # failure, no write
+ render_prof.error = e
raise
finally:
- if main_task:
- self._main_tasks.remove(main_task)
+ render_prof.profile_log.tick("returning tab")
+ await self._return_kaleido_tab(tab)
+ render_prof.profile_log.tick("tab returned")
- async def write_fig_from_object( # noqa: C901 too complex
+ ### API ###
+ @overload
+ async def write_fig_from_object(
self,
- generator: Iterable | AsyncIterable,
+ fig_dicts: FigureDict,
*,
- error_log: None | list[ErrorEntry] = None,
- profiler: None | list = None,
- ):
- """
- Equal to `write_fig` but allows the user to generate all arguments.
+ cancel_on_error: bool = False,
+ _write: Literal[False],
+ stepper: bool = False,
+ ) -> bytes: ...
- Generator must yield dictionaries with keys:
- - fig: the plotly figure
- - path: (optional, string or pathlib.Path) the path
- - opts: (optional) dictionary with:
- - format (string)
- - scale (number)
- - height (number)
- - and width (number)
- - topojson: (optional) topojsons are used to customize choropleths
+ @overload
+ async def write_fig_from_object(
+ self,
+ fig_dicts: FigureDict | AnyIterable[FigureDict],
+ *,
+ cancel_on_error: Literal[True],
+ _write: Literal[True] = True,
+ stepper: bool = False,
+ ) -> None: ...
- Generators are good because, if rendering many images, one doesn't need to
- prerender them all. They can be rendered and yielded asynchronously.
+ @overload
+ async def write_fig_from_object(
+ self,
+ fig_dicts: FigureDict | AnyIterable[FigureDict],
+ *,
+ cancel_on_error: Literal[False] = False,
+ _write: Literal[True] = True,
+ stepper: bool = False,
+ ) -> tuple[Exception]: ...
- While `write_fig` can also take generators, but only for the figure.
- In this case, the generator will specify all render-related arguments.
+ @overload
+ async def write_fig_from_object(
+ self,
+ fig_dicts: FigureDict | AnyIterable[FigureDict],
+ *,
+ cancel_on_error: bool,
+ _write: Literal[True] = True,
+ stepper: bool = False,
+ ) -> tuple[Exception] | None: ...
- Args:
- generator: an iterable or generator which supplies a dictionary
- of arguments to pass to tab.write_fig.
- error_log: A supplied list, will be populated with `ErrorEntry`s
- which can be converted to strings. Note, this is for
- collections errors that have to do with plotly. They will
- not be thrown. Lower level errors (kaleido, choreographer)
- will still be thrown.
- profiler: A supplied dictionary, will be populated with information
- about tabs, runtimes, etc.
+ async def write_fig_from_object(
+ self,
+ fig_dicts: FigureDict | AnyIterable[FigureDict],
+ *,
+ cancel_on_error=False,
+ _write: bool = True, # backwards compatibility!
+ stepper: bool = False,
+ ) -> None | bytes | tuple[Exception]:
+ """Temp."""
+ if not _write:
+ cancel_on_error = True
- """
- if error_log is not None:
- _logger.info("Using error log.")
- if profiler is not None:
- _logger.info("Using profiler.")
+ if _is_figuredict(fig_dicts):
+ fig_dicts = [fig_dicts]
+ name = "No Name"
if main_task := asyncio.current_task():
- self._main_tasks.add(main_task)
- tasks = set()
-
- async def _loop(args):
- spec, full_path = build_fig_spec(
- args.pop("fig"),
- args.pop("path", None),
- args.pop("opts", None),
- )
- args["spec"] = spec
- args["full_path"] = full_path
- tab = await self._get_kaleido_tab()
- if profiler is not None and tab.tab.target_id not in profiler:
- profiler[tab.tab.target_id] = []
- t = asyncio.create_task(
- self._render_task(
- tab,
- args=args,
- error_log=error_log,
- profiler=profiler,
- ),
- )
- t.add_done_callback(
- partial(
- self._check_render_task,
- full_path.name,
- tab,
- main_task,
- error_log,
- ),
- )
- tasks.add(t)
+ self._main_render_coroutines.add(main_task)
+ name = main_task.get_name()
+
+ profiler = _profiler.WriteCall(name)
+ self.profiler.append(profiler)
+
+ tasks: set[asyncio.Task] = set()
try:
- if hasattr(generator, "__aiter__"): # is async iterable
- _logger.debug("Is async for")
- async for args in generator:
- await _loop(args)
+ async for fig_arg in _utils.ensure_async_iter(fig_dicts):
+ t: asyncio.Task = asyncio.create_task(
+ self._render_task(
+ fig_arg=fig_arg,
+ topojson=fig_arg.get("topojson"),
+ _write=_write, # backwards compatibility
+ profiler=profiler,
+ stepper=stepper,
+ ),
+ )
+ tasks.add(t)
+ await asyncio.sleep(0) # this forces the added task to run
+
+ res = await asyncio.gather(*tasks, return_exceptions=not cancel_on_error)
+ if not _write:
+ return cast("bytes", res[0])
+ elif cancel_on_error:
+ return None
else:
- _logger.debug("Is sync for")
- for args in generator:
- await _loop(args)
- _logger.debug("awaiting tasks")
- await asyncio.gather(*tasks, return_exceptions=True)
- except:
- _logger.exception("Cleaning tasks after error.")
+ return cast("tuple[Exception]", tuple(r for r in res if r))
+
+ finally:
for task in tasks:
if not task.done():
task.cancel()
- raise
- finally:
if main_task:
- self._main_tasks.remove(main_task)
+ self._main_render_coroutines.remove(main_task)
+
+ async def write_fig( # noqa: PLR0913
+ self,
+ fig: fig_tools.Figurish,
+ path: None | Path | str = None,
+ opts: None | fig_tools.LayoutOpts = None,
+ *,
+ topojson: str | None = None,
+ cancel_on_error: bool = False,
+ stepper: bool = False,
+ ) -> tuple[Exception] | None:
+ """Temp."""
+ if fig_tools.is_figurish(fig) or not isinstance(
+ fig,
+ (Iterable, AsyncIterable),
+ ):
+ fig = [fig]
+
+ async def _temp_generator() -> AsyncGenerator[FigureDict, None]:
+ async for f in _utils.ensure_async_iter(fig):
+ yield {
+ "fig": f,
+ "path": path,
+ "opts": opts,
+ "topojson": topojson,
+ }
+
+ generator = cast("AsyncIterable[FigureDict]", _temp_generator())
+ return await self.write_fig_from_object(
+ fig_dicts=generator,
+ cancel_on_error=cancel_on_error,
+ stepper=stepper,
+ )
+
+ async def calc_fig(
+ self,
+ fig: fig_tools.Figurish,
+ opts: None | fig_tools.LayoutOpts = None,
+ *,
+ topojson: str | None = None,
+ stepper: bool = False,
+ ) -> bytes:
+ """Temp."""
+
+ async def _temp_generator():
+ yield {
+ "fig": fig,
+ "opts": opts,
+ "topojson": topojson,
+ }
+
+ return await self.write_fig_from_object(
+ fig_dicts=_temp_generator(),
+ cancel_on_error=True,
+ _write=False,
+ stepper=stepper,
+ )
diff --git a/src/py/kaleido/mocker/__init__.py b/src/py/kaleido/mocker/__init__.py
new file mode 100644
index 00000000..63aaae39
--- /dev/null
+++ b/src/py/kaleido/mocker/__init__.py
@@ -0,0 +1,69 @@
+"""Mocker is an integration-test utility."""
+
+from __future__ import annotations
+
+import asyncio
+import sys
+from random import sample
+from typing import TYPE_CHECKING
+
+import logistro
+
+import kaleido
+
+from . import _utils
+from ._args import args
+
+if TYPE_CHECKING:
+ from pathlib import Path
+
+_logger = logistro.getLogger(__name__)
+
+# ruff: noqa: T201 we print stuff.
+
+
+def random_config(paths: list[Path]) -> list[Path]:
+ """Select a portion of possible paths."""
+ if args.random > len(paths):
+ raise ValueError(
+ f"Input discover {len(paths)} paths, but a sampling of"
+ f"{args.random} was asked for.",
+ )
+ return sample(paths, args.random)
+
+
+# Function to process the images
+async def _main():
+ paths = _utils.get_jsons_in_paths(args.input)
+ if args.random:
+ paths = random_config(paths)
+
+ async with kaleido.Kaleido(
+ page_generator=kaleido.PageGenerator(force_cdn=True),
+ n=args.n,
+ headless=args.headless,
+ timeout=args.timeout,
+ ) as k:
+ return await k.write_fig_from_object(
+ _utils.load_figures_from_paths(paths),
+ stepper=args.stepper,
+ cancel_on_error=args.fail_fast,
+ ), k.profiler
+
+
+def main():
+ """[project.scripts] expects to call a function, not a module."""
+ errors, _profiler = asyncio.run(_main())
+ # do profile here
+ if errors:
+ # better to get this from the profile
+ print(f"Number of errors: {len(errors)}")
+ for i, e in enumerate(errors):
+ print(str(e), file=sys.stderr)
+ if i > 10: # noqa: PLR2004
+ print("More than 10 errors, use --profile.", file=sys.stderr)
+ break
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/py/kaleido/mocker/_args.py b/src/py/kaleido/mocker/_args.py
new file mode 100644
index 00000000..ac44e0f9
--- /dev/null
+++ b/src/py/kaleido/mocker/_args.py
@@ -0,0 +1,176 @@
+from __future__ import annotations
+
+import argparse
+import sys
+from pathlib import Path
+
+import logistro
+
+from . import _defaults
+
+_logger = logistro.getLogger(__name__)
+
+description = "\n".join( # noqa: FLY002
+ [
+ "kaleido_mocker loads & renders Plotly figures (from json or pickle).",
+ "",
+ "Furthermore, it outputs (to stdout) a JSON with performance information.",
+ "",
+ "",
+ (
+ "Note: non-headless mode often interferes with multi-process mode as "
+ "non-visible windows are often throttled"
+ ),
+ ],
+)
+
+if "--headless" in sys.argv and "--no-headless" in sys.argv:
+ raise ValueError(
+ "Choose either '--headless' or '--no-headless'.",
+ )
+
+parser = argparse.ArgumentParser(
+ add_help=True,
+ parents=[logistro.parser],
+ conflict_handler="resolve",
+ description=description,
+)
+
+# Overrides logstro default
+parser.add_argument(
+ "--logistro-level",
+ default="INFO",
+ dest="log",
+ help="Set the logging level (default INFO)",
+)
+
+basic_config = parser.add_argument_group("Basic Config Options")
+
+basic_config.add_argument(
+ "--n",
+ type=int,
+ default=_defaults.cpus,
+ help="Number of tabs, defaults to # of cpus",
+)
+basic_config.add_argument(
+ "--input",
+ type=str,
+ default=_defaults.in_dir,
+ help="Directory of mock file/s or single file (default tests/mocks)",
+)
+basic_config.add_argument(
+ "--output",
+ type=str,
+ default=_defaults.out_dir,
+ help="DIRECTORY of mock file/s (default tests/renders)",
+)
+
+basic_config.add_argument(
+ "--timeout",
+ type=int,
+ default=90,
+ help="Set timeout in seconds for any 1 mock (default 60 seconds)",
+)
+
+basic_config.add_argument(
+ "--fail-fast",
+ action="store_true",
+ default=False,
+ help="Throw first error encountered and stop execution.",
+)
+
+basic_config.add_argument(
+ "--random",
+ type=int,
+ default=0,
+ help="Will select N random jsons- or if 0 (default), all.",
+)
+
+# Image Setting Arguments
+
+image_parameters = parser.add_argument_group("Image Parameterize")
+
+image_parameters.add_argument(
+ "--parameterize",
+ action="store_true",
+ default=False,
+ help="Run mocks w/ different configurations.",
+)
+
+image_parameters.add_argument(
+ "--format",
+ type=str,
+ default=argparse.SUPPRESS,
+ help="png (default), pdf, jpg, webp, svg, json",
+)
+image_parameters.add_argument(
+ "--width",
+ type=str,
+ default=argparse.SUPPRESS,
+ help="width in pixels (default 700)",
+)
+image_parameters.add_argument(
+ "--height",
+ type=str,
+ default=argparse.SUPPRESS,
+ help="height in pixels (default 500)",
+)
+image_parameters.add_argument(
+ "--scale",
+ type=str,
+ default=argparse.SUPPRESS,
+ help="Scale ratio, acts as multiplier for height/width (default 1)",
+)
+
+# Diagnostic Arguments
+
+diagnostic_options = parser.add_argument_group("Diagnostic Options")
+
+diagnostic_options.add_argument(
+ "--headless",
+ action="store_true",
+ default=True,
+ help="Set headless as True (default)",
+)
+diagnostic_options.add_argument(
+ "--no-headless",
+ action="store_false",
+ dest="headless",
+ help="Set headless as False",
+)
+
+diagnostic_options.add_argument(
+ "--stepper",
+ action="store_true",
+ default=False,
+ dest="stepper",
+ help="Stepper sets n to 1, headless to False, no timeout "
+ "and asks for confirmation before printing.",
+)
+
+
+args = parser.parse_args()
+
+logistro.getLogger().setLevel(args.log)
+
+if not Path(args.output).is_dir():
+ raise ValueError(f"Specified output must be existing directory. Is {args.output!s}")
+
+args_d = vars(args)
+if args_d["stepper"]:
+ args_d["n"] = 1
+ args_d["headless"] = True
+ args_d["timeout"] = 0
+
+_p = args_d["parameterize"]
+
+args_d.setdefault("width", _defaults.width if _p else _defaults.width[0])
+args_d.setdefault("height", _defaults.height if _p else _defaults.height[0])
+args_d.setdefault("scale", _defaults.scale if _p else _defaults.scale[0])
+args_d.setdefault("format", _defaults.extension if _p else _defaults.extension[0])
+
+for key in ("width", "height", "scale", "format"):
+ if not isinstance(args_d[key], (list, tuple)):
+ args_d[key] = (args_d[key],)
+
+_logger.info(f"Mocker args calculated: {args_d}")
diff --git a/src/py/kaleido/mocker/_defaults.py b/src/py/kaleido/mocker/_defaults.py
new file mode 100644
index 00000000..1df94a75
--- /dev/null
+++ b/src/py/kaleido/mocker/_defaults.py
@@ -0,0 +1,31 @@
+from __future__ import annotations
+
+import multiprocessing
+from pathlib import Path
+
+import logistro
+
+_logger = logistro.getLogger(__name__)
+
+
+width = [700, 200, 1000] # first value in main default if not parameterized
+height = [500, 200, 1000]
+scale = [1, 0.5, 2]
+extension = [
+ "png",
+ "pdf",
+ "jpg",
+ "webp",
+ "svg",
+ "json",
+]
+
+# use itertools.product
+
+# Number of CPUS
+cpus = multiprocessing.cpu_count()
+
+# Default Directories
+test_dir = Path(__file__).resolve().parent.parent.parent / "integration_tests"
+in_dir = test_dir / "mocks"
+out_dir = test_dir / "renders"
diff --git a/src/py/kaleido/mocker/_utils.py b/src/py/kaleido/mocker/_utils.py
new file mode 100644
index 00000000..125dbed0
--- /dev/null
+++ b/src/py/kaleido/mocker/_utils.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+
+import itertools
+from pathlib import Path
+from typing import TYPE_CHECKING, TypedDict
+
+import logistro
+import orjson
+
+from ._args import args
+
+if TYPE_CHECKING:
+ ...
+
+_logger = logistro.getLogger(__name__)
+
+
+def get_jsons_in_paths(path: str | Path) -> list[Path]:
+ # Work with Paths and directories
+ path = Path(path) if isinstance(path, str) else path
+
+ if path.is_dir():
+ _logger.info(f"Input is path {path}")
+ return list(path.glob("*.json"))
+ elif path.is_file():
+ _logger.info(f"Input is file {path}")
+ return [path]
+ else:
+ raise TypeError("--input must be file or directory")
+
+
+class Param(TypedDict):
+ name: str
+ opts: dict[str, int | float]
+
+
+# maybe don't have this do params and figures
+def load_figures_from_paths(paths: list[Path]):
+ # Set json
+ for path in paths:
+ if not path.is_file():
+ raise RuntimeError(f"Path {path} is not a file.")
+ _logger.info(f"Found file: {path!s}")
+ with path.open(encoding="utf-8") as file:
+ figure = orjson.loads(file.read())
+ for f, w, h, s in itertools.product( # all combos
+ args.format,
+ args.width,
+ args.height,
+ args.scale,
+ ):
+ name = (
+ f"{path.stem}.{f!s}"
+ if not args.parameterize
+ else f"{path.stem!s}-{w!s}x{h!s}@{s!s}.{f!s}"
+ )
+ opts = {
+ "scale": s,
+ "width": w,
+ "height": h,
+ }
+ _logger.info(f"Yielding spec: {name!s}")
+ yield {
+ "fig": figure,
+ "path": str(Path(args.output) / name),
+ "opts": opts,
+ }
diff --git a/src/py/pyproject.toml b/src/py/pyproject.toml
index c0436a8e..d44301ac 100644
--- a/src/py/pyproject.toml
+++ b/src/py/pyproject.toml
@@ -38,7 +38,7 @@ Homepage = "https://github.com/plotly/kaleido"
Repository = "https://github.com/plotly/kaleido"
[project.scripts]
-kaleido_mocker = "kaleido._mocker:build_mocks"
+kaleido_mocker = "kaleido.mocker:main"
kaleido_get_chrome = "choreographer.cli._cli_utils:get_chrome_cli"
[dependency-groups]
@@ -54,7 +54,9 @@ dev = [
"pandas>=2.0.3",
"typing-extensions>=4.12.2",
"hypothesis>=6.113.0",
+ "pyright>=1.1.406",
]
+
pickles = [
"colorcet>=3.1.0",
"datashader>=0.15.2",
@@ -82,6 +84,7 @@ ignore = [
"SIM105", # Too opionated (try-except-pass)
"PT003", # scope="function" implied but I like readability
"G004", # fstrings in my logs
+ "TD003", # issue link in todos
]
[tool.ruff.lint.per-file-ignores]
@@ -102,7 +105,7 @@ log_cli = false
# name = cmd
[tool.poe.tasks.test]
-cmd = "pytest --timeout=90 --log-level=1 -W error -n auto -v -rfE --capture=fd"
+cmd = "pytest --timeout=50 --log-level=1 -W error -n auto -v -rfE --capture=fd"
help = "Run all tests quickly"
[tool.poe.tasks.debug-test]
@@ -113,3 +116,8 @@ help = "Run test by test, slowly, quitting after first error"
[tool.poe.tasks.filter-test]
cmd = "pytest --log-level=1 -W error -vvvx -rA --capture=no --show-capture=no"
help = "Run any/all tests one by one with basic settings: can include filename and -k filters"
+
+[tool.pyright]
+venvPath = "."
+venv = ".venv"
+exclude= ["src/py/integration_tests/dates/*"]
diff --git a/src/py/tests/README.md b/src/py/tests/README.md
new file mode 100644
index 00000000..a332a06a
--- /dev/null
+++ b/src/py/tests/README.md
@@ -0,0 +1,14 @@
+
+To clarify three similar test files:
+
+- test_public_api.py does render tests of renderers in __init__.py
+ - it does not parametrize
+- test_init.py tests that wrappers are proper and pass args/kwargs
+ - it does parameterize
+- test_kaleido tests the substantial parts of kaleido and its returns
+ - it has incomplete parameterizing
+
+
+
+Parameterizing actual renders would be a huge burden, so integration tests
+with mocks are currently the most complete tests.
diff --git a/src/py/tests/test_calc_fig_one_off.py b/src/py/tests/test_calc_fig_one_off.py
index 70ae170f..233260e1 100644
--- a/src/py/tests/test_calc_fig_one_off.py
+++ b/src/py/tests/test_calc_fig_one_off.py
@@ -13,7 +13,7 @@
async def test_calc_fig():
# ruff: noqa: PLC0415
- import plotly.express as px
+ import plotly.express as px # type: ignore[import-untyped]
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
diff --git a/src/py/tests/test_fig_tools.py b/src/py/tests/test_fig_tools.py
index 4fc8be11..c170ff61 100644
--- a/src/py/tests/test_fig_tools.py
+++ b/src/py/tests/test_fig_tools.py
@@ -1,26 +1,30 @@
-from pathlib import Path
-
import pytest
-from kaleido import _fig_tools
+from kaleido._utils import fig_tools
sources = ["argument", "layout", "template", "default"]
values = [None, 150, 800, 1500]
+values2 = [None, 300, 1000, 1300]
@pytest.mark.parametrize("width_source", sources)
@pytest.mark.parametrize("height_source", sources)
@pytest.mark.parametrize("width_value", values)
-@pytest.mark.parametrize("height_value", [x * 1.5 if x else x for x in values])
-def test_get_figure_dimensions(width_source, height_source, width_value, height_value):
- """Test _get_figure_dimensions with all combinations of width/height sources."""
+@pytest.mark.parametrize("height_value", values2)
+def test_coerce_for_js_dimensions(
+ width_source,
+ height_source,
+ width_value,
+ height_value,
+):
+ """Test coerce_for_js with all combinations of width/height sources."""
layout = {}
- width_arg = None
+ opts = {}
expected_width = width_value
if width_source == "argument":
- width_arg = width_value
+ opts["width"] = width_value
elif width_source == "layout":
layout["width"] = width_value
elif width_source == "template":
@@ -32,14 +36,13 @@ def test_get_figure_dimensions(width_source, height_source, width_value, height_
# Set to default if None
if expected_width is None:
- expected_width = _fig_tools.DEFAULT_WIDTH
+ expected_width = fig_tools.DEFAULT_WIDTH
# Do for height what I did for width
- height_arg = None
expected_height = height_value
if height_source == "argument":
- height_arg = height_value
+ opts["height"] = height_value
elif height_source == "layout":
layout["height"] = height_value
elif height_source == "template":
@@ -51,173 +54,20 @@ def test_get_figure_dimensions(width_source, height_source, width_value, height_
# Set to default if None
if expected_height is None:
- expected_height = _fig_tools.DEFAULT_HEIGHT
+ expected_height = fig_tools.DEFAULT_HEIGHT
+
+ # Create a figure dict with the layout
+ fig = {"data": [], "layout": layout}
# Call the function
- r_width, r_height = _fig_tools._get_figure_dimensions( # noqa: SLF001
- layout,
- width_arg,
- height_arg,
- )
+ spec = fig_tools.coerce_for_js(fig, None, opts)
# Assert results
- assert r_width == expected_width, (
- f"Width mismatch: got {r_width}, expected {expected_width}, "
+ assert spec["width"] == expected_width, (
+ f"Width mismatch: got {spec['width']}, expected {expected_width}, "
f"source: {width_source}, value: {width_value}"
)
- assert r_height == expected_height, (
- f"Height mismatch: got {r_height}, expected {expected_height}, "
+ assert spec["height"] == expected_height, (
+ f"Height mismatch: got {spec['height']}, expected {expected_height}, "
f"source: {height_source}, value: {height_value}"
)
-
-
-def test_next_filename_no_existing_files(tmp_path):
- """Test _next_filename when no files exist."""
- result = _fig_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
- assert result == "test.png"
-
-
-def test_next_filename_base_file_exists(tmp_path):
- """Test _next_filename when base file exists."""
- # Create the base file
- (tmp_path / "test.png").touch()
-
- result = _fig_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
- assert result == "test-2.png"
-
-
-def test_next_filename_numbered_files_exist(tmp_path):
- """Test _next_filename when numbered files exist."""
- # Create various numbered files
- (tmp_path / "test.png").touch()
- (tmp_path / "test-2.png").touch()
- (tmp_path / "test-3.png").touch()
- (tmp_path / "test-5.png").touch() # Gap in numbering
-
- result = _fig_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
- assert result == "test-6.png" # Should be max + 1
-
-
-def test_next_filename_similar_names_ignored(tmp_path):
- """Test _next_filename ignores files with similar but different names."""
- # Create files that shouldn't match the pattern
- (tmp_path / "test.png").touch()
- (tmp_path / "test-2.png").touch()
- (tmp_path / "testing-3.png").touch() # Different prefix
- (tmp_path / "test-2.jpg").touch() # Different extension
- (tmp_path / "test-abc.png").touch() # Non-numeric suffix
-
- result = _fig_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
- assert result == "test-3.png" # Should only count test.png and test-2.png
-
-
-def test_next_filename_special_characters(tmp_path):
- """Test _next_filename with special characters in prefix and extension."""
- prefix = "test-file_name"
- ext = "svg" # set up to be parameterized but not
-
- # Create some files
- (tmp_path / f"{prefix}.{ext}").touch()
- (tmp_path / f"{prefix}-2.{ext}").touch()
-
- result = _fig_tools._next_filename(tmp_path, prefix, ext) # noqa: SLF001
- assert result == f"{prefix}-3.{ext}"
-
-
-def test_next_filename_only_numbered_files(tmp_path):
- """Test _next_filename when only numbered files exist (no base file)."""
- # Create only numbered files, no base file
- (tmp_path / "test-2.png").touch()
- (tmp_path / "test-3.png").touch()
- (tmp_path / "test-10.png").touch()
-
- result = _fig_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
- assert result == "test-11.png" # Should be max + 1
-
-
-# Fixtures for _build_full_path tests - testing various title scenarios
-@pytest.fixture(
- params=[
- (
- {
- "layout": {
- "title": {"text": "My-Test!@#$%^&*()Chart_with[lots]of{symbols}"},
- },
- },
- "My_TestChart_withlotsofsymbols",
- ), # Complex title
- (
- {"layout": {"title": {"text": "Simple Title"}}},
- "Simple_Title",
- ), # Simple title
- ({"layout": {}}, "fig"), # No title
- ],
-)
-def fig_fixture(request):
- """Parameterized fixture for fig with various title scenarios."""
- return request.param
-
-
-def test_build_full_path_no_path_input(fig_fixture):
- """Test _build_full_path with no path input uses current path."""
- fig_dict, expected_prefix = fig_fixture
- result = _fig_tools._build_full_path(None, fig_dict, "ext") # noqa: SLF001
-
- # Should use current directory
- assert result.parent.resolve() == Path().cwd().resolve()
- assert result.parent.is_dir()
-
- assert result.name == f"{expected_prefix}.ext"
-
-
-def test_build_full_path_no_suffix_directory(tmp_path, fig_fixture):
- """Test _build_full_path with path having no suffix."""
- fig_dict, expected_prefix = fig_fixture
-
- # Test directory no suffix
- test_dir = tmp_path
- result = _fig_tools._build_full_path(test_dir, fig_dict, "ext") # noqa: SLF001
-
- # Should use provided directory
- assert result.parent == test_dir
- assert result.name == f"{expected_prefix}.ext"
-
- # Test error
- nonexistent_dir = Path("/nonexistent/directory")
- with pytest.raises(ValueError, match=r"Directory .* not found. Please create it."):
- _fig_tools._build_full_path(nonexistent_dir, fig_dict, "ext") # noqa: SLF001
-
-
-def test_build_full_path_directory_with_suffix(tmp_path, fig_fixture):
- """Test _build_full_path with path that is directory even with suffix."""
- fig_dict, expected_prefix = fig_fixture
-
- # Create a directory with a suffix-like name
- dir_with_suffix = tmp_path / "mydir.png"
- dir_with_suffix.mkdir()
-
- result = _fig_tools._build_full_path(dir_with_suffix, fig_dict, "ext") # noqa: SLF001
-
- # Should treat as directory
- assert result.parent == dir_with_suffix
- assert result.name == f"{expected_prefix}.ext"
-
-
-def test_build_full_path_file_with_suffix(tmp_path, fig_fixture):
- """Test _build_full_path with file path having suffix."""
- fig_dict, _expected_prefix = fig_fixture
-
- # Exists
- file_path = tmp_path / "output.png"
- result = _fig_tools._build_full_path(file_path, fig_dict, "ext") # noqa: SLF001
-
- # Should return the exact path provided
- assert result == file_path
-
- # Doesn't exist
- file_path = Path("/nonexistent/directory/output.png")
- with pytest.raises(
- RuntimeError,
- match=r"Cannot reach path .* Are all directories created?",
- ):
- _fig_tools._build_full_path(file_path, fig_dict, "ext") # noqa: SLF001
diff --git a/src/py/tests/test_init.py b/src/py/tests/test_init.py
index 79fb5ba3..3574be76 100644
--- a/src/py/tests/test_init.py
+++ b/src/py/tests/test_init.py
@@ -1,15 +1,21 @@
"""Tests for wrapper functions in __init__.py that test argument passing."""
+import subprocess
+import sys
+from pathlib import Path
+from typing import TYPE_CHECKING
from unittest.mock import AsyncMock, patch
import pytest
import kaleido
-# Pretty complicated for basically testing a bunch of wrappers, but it works.
-# Integration tests seem more important.
-# I much prefer the public_api file, this set of tests can be considered
-# for deletion.
+if TYPE_CHECKING:
+ from kaleido._utils.fig_tools import Figurish
+
+
+# Just tests wrapping, but in a way tests internals.
+# These are better done as part of integration tests.
@pytest.fixture
@@ -24,6 +30,16 @@ def kwargs():
return {"width": 800}
+# line serves to force static check of string in @patch
+_ = kaleido._sync_server.GlobalKaleidoServer.open # noqa: SLF001
+
+
+def test_hangers():
+ folder = Path(__file__).parent / "win_hang_scripts"
+ subprocess.run([sys.executable, str(folder / "open_close.py")], check=True) # noqa: S603
+ subprocess.run([sys.executable, str(folder / "open.py")], check=True) # noqa: S603
+
+
@patch("kaleido._sync_server.GlobalKaleidoServer.open")
def test_start_sync_server_passes_args(mock_open, args, kwargs):
"""Test that start_sync_server passes args and silence_warnings correctly."""
@@ -37,6 +53,10 @@ def test_start_sync_server_passes_args(mock_open, args, kwargs):
mock_open.assert_called_with(*args, silence_warnings=True, **kwargs)
+# line serves to force static check of string in @patch
+_ = kaleido._sync_server.GlobalKaleidoServer.close # noqa: SLF001
+
+
@patch("kaleido._sync_server.GlobalKaleidoServer.close")
def test_stop_sync_server_passes_args(mock_close):
"""Test that stop_sync_server passes silence_warnings correctly."""
@@ -50,6 +70,10 @@ def test_stop_sync_server_passes_args(mock_close):
mock_close.assert_called_with(silence_warnings=True)
+# line serves to force static check of string in @patch
+_ = kaleido.Kaleido
+
+
@patch("kaleido.Kaleido")
async def test_async_wrapper_functions(mock_kaleido_class):
"""Test all async wrapper functions pass arguments correctly.
@@ -71,13 +95,17 @@ async def test_async_wrapper_functions(mock_kaleido_class):
topojson = "test_topojson"
kopts = {"some_option": "value"}
- result = await kaleido.calc_fig(fig, path, opts, topojson=topojson, kopts=kopts)
+ result = await kaleido.calc_fig(
+ fig,
+ opts, # type: ignore[reportArgumentType]
+ topojson=topojson,
+ kopts=kopts,
+ )
expected_kopts = {"some_option": "value", "n": 1}
mock_kaleido_class.assert_called_with(**expected_kopts)
mock_kaleido.calc_fig.assert_called_with(
fig,
- path=path,
opts=opts,
topojson=topojson,
)
@@ -96,7 +124,13 @@ async def test_async_wrapper_functions(mock_kaleido_class):
mock_kaleido.write_fig.reset_mock()
# Test write_fig with full arguments
- await kaleido.write_fig(fig, path, opts, topojson=topojson, kopts=kopts)
+ await kaleido.write_fig(
+ fig,
+ path,
+ opts, # type: ignore[reportArgumentType]
+ topojson=topojson,
+ kopts=kopts,
+ )
mock_kaleido_class.assert_called_with(**kopts) # write_fig doesn't force n=1
mock_kaleido.write_fig.assert_called_with(
fig,
@@ -118,12 +152,17 @@ async def test_async_wrapper_functions(mock_kaleido_class):
mock_kaleido.write_fig_from_object.reset_mock()
# Test write_fig_from_object
- generator = [{"data": []}]
+ generator: list[Figurish] = [{"data": []}]
await kaleido.write_fig_from_object(generator, kopts=kopts)
mock_kaleido_class.assert_called_with(**kopts)
mock_kaleido.write_fig_from_object.assert_called_with(generator)
+# line serves to force static check of string in @patch
+_ = kaleido._sync_server.GlobalKaleidoServer.is_running # noqa: SLF001
+_ = kaleido._sync_server.GlobalKaleidoServer.call_function # noqa: SLF001
+
+
@patch("kaleido._sync_server.GlobalKaleidoServer.is_running")
@patch("kaleido._sync_server.GlobalKaleidoServer.call_function")
def test_sync_wrapper_server(mock_call_function, mock_is_running, args, kwargs):
@@ -147,6 +186,11 @@ def test_sync_wrapper_server(mock_call_function, mock_is_running, args, kwargs):
mock_call_function.assert_called_with("write_fig_from_object", *args, **kwargs)
+# line serves to force static check of string in @patch
+_ = kaleido._sync_server.GlobalKaleidoServer.is_running # noqa: SLF001
+_ = kaleido._sync_server.oneshot_async_run # noqa: SLF001
+
+
@patch("kaleido._sync_server.GlobalKaleidoServer.is_running")
@patch("kaleido._sync_server.oneshot_async_run")
def test_sync_wrapper_oneshot(mock_oneshot_run, mock_is_running, args, kwargs):
diff --git a/src/py/tests/test_kaleido.py b/src/py/tests/test_kaleido.py
index 6b178dce..84424d2d 100644
--- a/src/py/tests/test_kaleido.py
+++ b/src/py/tests/test_kaleido.py
@@ -1,18 +1,21 @@
import asyncio
import re
-from unittest.mock import patch
+from unittest.mock import AsyncMock, patch
import pytest
-from hypothesis import HealthCheck, Phase, given, settings
+from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st
from kaleido import Kaleido
-@pytest.fixture
+# can't do session scope because pytest complains that its used by
+# function-scoped loops. tried to create a separate loop in here with
+# session, lots of spooky errors, even asyncio.run() doesn't clean up right.
+@pytest.fixture(scope="function")
async def simple_figure_with_bytes():
"""Create a simple figure with calculated bytes and PNG assertion."""
- import plotly.express as px # noqa: PLC0415
+ import plotly.express as px # type: ignore[import-untyped] # noqa: PLC0415
fig = px.line(x=[1, 2, 3], y=[1, 2, 3])
@@ -117,52 +120,77 @@ async def test_write_fig_from_object_iterator(simple_figure_with_bytes, tmp_path
)
+async def test_write_fig_from_object_return_modes(simple_figure_with_bytes, tmp_path):
+ """Test write_fig_from_object with different return schemes."""
+
+ fig_list = []
+ file_paths = []
+ for i in range(2):
+ path = tmp_path / "does_not_exist" / f"test_iter_{i}.png"
+ file_paths.append(path)
+ fig_list.append(
+ {
+ "fig": simple_figure_with_bytes["fig"],
+ "path": path,
+ "opts": simple_figure_with_bytes["opts"],
+ },
+ )
+
+ # test collecting errors
+ async with Kaleido() as k:
+ res = await k.write_fig_from_object(fig_list, cancel_on_error=False)
+ for r in res:
+ assert isinstance(r, RuntimeError)
+ assert len(res) == len(fig_list)
+
+ # test not collecting errors
+ with pytest.raises(RuntimeError):
+ async with Kaleido() as k:
+ res = await k.write_fig_from_object(fig_list, cancel_on_error=True)
+
+ # test returning
+ async with Kaleido() as k:
+ res = await k.write_fig_from_object(fig_list[0], _write=False)
+ assert res == simple_figure_with_bytes["bytes"]
+
+ # Assert that each created file matches the fixture bytes
+ for path in file_paths:
+ assert not path.exists()
+
+
async def test_write_fig_from_object_bare_dictionary(
simple_figure_with_bytes,
tmp_path,
):
- """Test write_fig_from_object with bare dictionary list."""
+ """Test write_fig_from_object with bare dictionary."""
path1 = tmp_path / "test_dict_1.png"
- path2 = tmp_path / "test_dict_2.png"
-
- fig_data = [
- {
- "fig": simple_figure_with_bytes["fig"],
- "path": path1,
- "opts": simple_figure_with_bytes["opts"],
- },
- {
- "fig": simple_figure_with_bytes["fig"].to_dict(),
- "path": path2,
- "opts": simple_figure_with_bytes["opts"],
- },
- ]
+
+ fig_data = {
+ "fig": simple_figure_with_bytes["fig"],
+ "path": path1,
+ "opts": simple_figure_with_bytes["opts"],
+ }
async with Kaleido() as k:
await k.write_fig_from_object(fig_data)
# Assert that each created file matches the fixture bytes
- for path in [path1, path2]:
- assert path.exists(), f"File {path} was not created"
- created_bytes = path.read_bytes()
- assert created_bytes == simple_figure_with_bytes["bytes"], (
- f"File {path} bytes don't match fixture bytes"
- )
+ assert path1.exists(), f"File {path1} was not created"
+ created_bytes = path1.read_bytes()
+ assert created_bytes == simple_figure_with_bytes["bytes"], (
+ f"File {path1} bytes don't match fixture bytes"
+ )
-# In the refactor, all figure generation methods are really just wrappers
-# for the most flexible, tested above, generate_fig_from_object.
-# So we test that one, and then test to make sure its receiving arguments
-# properly for the other tests.
+@pytest.fixture(scope="function")
+def test_kaleido(): # speed up hypothesis test using a function fixture
+ return Kaleido()
-# Uncomment these settings after refactor.
-# @settings(suppress_health_check=[HealthCheck.function_scoped_fixture])
@settings(
- phases=[Phase.generate],
- max_examples=1,
suppress_health_check=[HealthCheck.function_scoped_fixture],
+ max_examples=50,
)
@given(
path=st.text(
@@ -175,8 +203,17 @@ async def test_write_fig_from_object_bare_dictionary(
format_type=st.sampled_from(["png", "svg", "pdf", "html"]),
topojson=st.one_of(st.none(), st.text(min_size=1, max_size=20)),
)
+@pytest.mark.parametrize(
+ "cancel_on_error",
+ [
+ True,
+ False,
+ ],
+ ids=("cancel_on_error", "collect_errors"),
+)
async def test_write_fig_argument_passthrough( # noqa: PLR0913
- simple_figure_with_bytes,
+ test_kaleido,
+ cancel_on_error,
tmp_path,
path,
width,
@@ -184,32 +221,33 @@ async def test_write_fig_argument_passthrough( # noqa: PLR0913
format_type,
topojson,
):
- """Test that write_fig properly passes arguments to write_fig_from_object."""
- pytest.skip("Remove this failure line and the comment above after the refactor!")
test_path = tmp_path / f"{path}.{format_type}"
opts = {"format": format_type, "width": width, "height": height}
-
+ fig = {"data": "test"}
# Mock write_fig_from_object to capture arguments
- with patch.object(Kaleido, "write_fig_from_object") as mock_write_fig_from_object:
- async with Kaleido() as k:
- await k.write_fig(
- simple_figure_with_bytes["fig"],
- path=test_path,
- opts=opts,
- topojson=topojson,
- )
-
+ with patch.object(
+ Kaleido,
+ "write_fig_from_object",
+ new=AsyncMock(return_value=[]),
+ ) as mock_write_fig_from_object:
+ await test_kaleido.write_fig(
+ fig,
+ path=test_path,
+ opts=opts,
+ topojson=topojson,
+ cancel_on_error=cancel_on_error,
+ )
# Verify write_fig_from_object was called
mock_write_fig_from_object.assert_called_once()
# Extract the generator that was passed as first argument
- args, _kwargs = mock_write_fig_from_object.call_args # not sure.
- assert len(args) == 1, "Expected exactly one argument (the generator)"
+ _, kwargs = mock_write_fig_from_object.call_args # not sure.
- generator = args[0]
+ generator = kwargs["fig_dicts"]
+ assert kwargs["cancel_on_error"] == cancel_on_error
# Convert generator to list to inspect its contents
- generated_args_list = list(generator)
+ generated_args_list = [v async for v in generator]
assert len(generated_args_list) == 1, (
"Expected generator to yield exactly one item"
)
@@ -223,14 +261,71 @@ async def test_write_fig_argument_passthrough( # noqa: PLR0913
assert "topojson" in generated_args, "Generated args should contain 'topojson'"
# Check that the values match
- assert generated_args["fig"] == simple_figure_with_bytes["fig"], (
- "Figure should match"
- )
+ assert generated_args["fig"] == fig, "Figure should match"
assert str(generated_args["path"]) == str(test_path), "Path should match"
assert generated_args["opts"] == opts, "Options should match"
assert generated_args["topojson"] == topojson, "Topojson should match"
+@settings(
+ suppress_health_check=[HealthCheck.function_scoped_fixture],
+ max_examples=50,
+)
+@given(
+ width=st.integers(min_value=100, max_value=2000),
+ height=st.integers(min_value=100, max_value=2000),
+ format_type=st.sampled_from(["png", "svg", "pdf", "html"]),
+ topojson=st.one_of(st.none(), st.text(min_size=1, max_size=20)),
+)
+async def test_calc_fig_argument_passthrough(
+ test_kaleido,
+ width,
+ height,
+ format_type,
+ topojson,
+):
+ opts = {"format": format_type, "width": width, "height": height}
+ fig = {"data": "test"}
+ # Mock write_fig_from_object to capture arguments
+ with patch.object(
+ Kaleido,
+ "write_fig_from_object",
+ new=AsyncMock(return_value=[]),
+ ) as mock_write_fig_from_object:
+ await test_kaleido.calc_fig(
+ fig,
+ opts=opts,
+ topojson=topojson,
+ )
+ # Verify write_fig_from_object was called
+ mock_write_fig_from_object.assert_called_once()
+
+ # Extract the generator that was passed as first argument
+ _, kwargs = mock_write_fig_from_object.call_args # not sure.
+
+ generator = kwargs["fig_dicts"]
+ assert kwargs["cancel_on_error"] is True
+ assert kwargs["_write"] is False
+
+ # Convert generator to list to inspect its contents
+ generated_args_list = [v async for v in generator]
+ assert len(generated_args_list) == 1, (
+ "Expected generator to yield exactly one item"
+ )
+
+ generated_args = generated_args_list[0]
+
+ # Validate that the generated arguments match what we passed to write_fig
+ assert "fig" in generated_args, "Generated args should contain 'fig'"
+ assert "opts" in generated_args, "Generated args should contain 'opts'"
+ assert "topojson" in generated_args, "Generated args should contain 'topojson'"
+
+ # Check that the values match
+ assert generated_args["fig"] == fig, "Figure should match"
+ assert generated_args["opts"] == opts, "Options should match"
+ assert generated_args["topojson"] == topojson, "Topojson should match"
+
+
async def test_kaleido_instantiate_no_hang():
"""Test that instantiating Kaleido doesn't hang."""
_ = Kaleido()
@@ -340,7 +435,7 @@ async def test_unreasonable_timeout(simple_figure_with_bytes):
opts = simple_figure_with_bytes["opts"]
# Use an infinitely small timeout
- async with Kaleido(timeout=0.000001) as k:
+ async with Kaleido(timeout=0.005) as k:
with pytest.raises((asyncio.TimeoutError, TimeoutError)):
await k.calc_fig(fig, opts=opts)
diff --git a/src/py/tests/test_page_generator.py b/src/py/tests/test_page_generator.py
index 3f7e4efc..f5b7907e 100644
--- a/src/py/tests/test_page_generator.py
+++ b/src/py/tests/test_page_generator.py
@@ -216,7 +216,7 @@ async def test_force_cdn():
"""Test force_cdn=True forces use of CDN plotly even when plotly is available."""
# Verify plotly is available first
if not find_spec("plotly"):
- pytest.skip("Plotly not available - cannot test force_cdn override")
+ pytest.fail("Plotly not available - cannot test force_cdn override")
forced_cdn = PageGenerator(force_cdn=True).generate_index()
scripts, _encodings = get_scripts_from_html(forced_cdn)
@@ -358,6 +358,10 @@ async def test_combined_overrides(tmp_path, data):
assert len(scripts) == expected_count
+# note: the logic below was extracted to utilities,
+# so in a way its tested twice since tests were developed for that file
+
+
# Test file path validation
async def test_existing_file_path(temp_js_file):
"""Test that existing file paths work with and without file:/// protocol."""
@@ -383,6 +387,7 @@ async def test_existing_file_path(temp_js_file):
async def test_nonexistent_file_path_raises_error(
nonexistent_file_path,
nonexistent_file_uri,
+ tmp_path,
):
"""Test that nonexistent file paths raise FileNotFoundError."""
# Test with regular path
@@ -396,10 +401,15 @@ async def test_nonexistent_file_path_raises_error(
with pytest.raises(FileNotFoundError):
PageGenerator(plotly=nonexistent_file_uri)
+ # Test that existing directory raises error
+ with pytest.raises(FileNotFoundError):
+ PageGenerator(plotly=str(tmp_path))
+
async def test_mathjax_nonexistent_file_raises_error(
nonexistent_file_path,
nonexistent_file_uri,
+ tmp_path,
):
"""Test that nonexistent mathjax file raises FileNotFoundError."""
# Test with regular path
@@ -413,10 +423,15 @@ async def test_mathjax_nonexistent_file_raises_error(
with pytest.raises(FileNotFoundError):
PageGenerator(mathjax=nonexistent_file_uri)
+ # Test that existing directory raises error
+ with pytest.raises(FileNotFoundError):
+ PageGenerator(mathjax=str(tmp_path))
+
async def test_others_nonexistent_file_raises_error(
nonexistent_file_path,
nonexistent_file_uri,
+ tmp_path,
):
"""Test that nonexistent file in others list raises FileNotFoundError."""
# Test with regular path
@@ -430,6 +445,10 @@ async def test_others_nonexistent_file_raises_error(
with pytest.raises(FileNotFoundError):
PageGenerator(others=[nonexistent_file_uri])
+ # Test that existing directory raises error
+ with pytest.raises(FileNotFoundError):
+ PageGenerator(others=[str(tmp_path)])
+
# Test HTTP URLs (should not raise FileNotFoundError)
async def test_http_urls_skip_file_validation():
diff --git a/src/py/tests/test_path_tools.py b/src/py/tests/test_path_tools.py
new file mode 100644
index 00000000..e3c9ad1c
--- /dev/null
+++ b/src/py/tests/test_path_tools.py
@@ -0,0 +1,157 @@
+from pathlib import Path
+
+import pytest
+
+from kaleido._utils import path_tools
+
+
+def test_next_filename_no_existing_files(tmp_path):
+ """Test _next_filename when no files exist."""
+ result = path_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
+ assert result == "test.png"
+
+
+def test_next_filename_base_file_exists(tmp_path):
+ """Test _next_filename when base file exists."""
+ # Create the base file
+ (tmp_path / "test.png").touch()
+
+ result = path_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
+ assert result == "test-2.png"
+
+
+def test_next_filename_numbered_files_exist(tmp_path):
+ """Test _next_filename when numbered files exist."""
+ # Create various numbered files
+ (tmp_path / "test.png").touch()
+ (tmp_path / "test-2.png").touch()
+ (tmp_path / "test-3.png").touch()
+ (tmp_path / "test-5.png").touch() # Gap in numbering
+
+ result = path_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
+ assert result == "test-6.png" # Should be max + 1
+
+
+def test_next_filename_similar_names_ignored(tmp_path):
+ """Test _next_filename ignores files with similar but different names."""
+ # Create files that shouldn't match the pattern
+ (tmp_path / "test.png").touch()
+ (tmp_path / "test-2.png").touch()
+ (tmp_path / "testing-3.png").touch() # Different prefix
+ (tmp_path / "test-2.jpg").touch() # Different extension
+ (tmp_path / "test-abc.png").touch() # Non-numeric suffix
+
+ result = path_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
+ assert result == "test-3.png" # Should only count test.png and test-2.png
+
+
+def test_next_filename_special_characters(tmp_path):
+ """Test _next_filename with special characters in prefix and extension."""
+ prefix = "test-f$ile_name"
+ ext = "s$v&g" # set up to be parameterized but not
+
+ # Create some files
+ (tmp_path / f"{prefix}.{ext}").touch()
+ (tmp_path / f"{prefix}-2.{ext}").touch()
+
+ result = path_tools._next_filename(tmp_path, prefix, ext) # noqa: SLF001
+ assert result == f"{prefix}-3.{ext}"
+
+
+def test_next_filename_only_numbered_files(tmp_path):
+ """Test _next_filename when only numbered files exist (no base file)."""
+ # Create only numbered files, no base file
+ (tmp_path / "test-2.png").touch()
+ (tmp_path / "test-3.png").touch()
+ (tmp_path / "test-10.png").touch()
+
+ result = path_tools._next_filename(tmp_path, "test", "png") # noqa: SLF001
+ assert result == "test-11.png" # Should be max + 1
+
+
+# Fixtures for determine_path tests - testing various title scenarios
+@pytest.fixture(
+ params=[
+ (
+ {
+ "layout": {
+ "title": {"text": "My-Test!@#$%^&()Chart_with[lots]of{symbols}"},
+ },
+ },
+ "My_TestChart_withlotsofsymbols",
+ ), # Complex title
+ (
+ {"layout": {"title": {"text": "Simple Title"}}},
+ "Simple_Title",
+ ), # Simple title
+ ({"layout": {}}, "fig"), # No title
+ ],
+)
+def fig_fixture(request):
+ """Parameterized fixture for fig with various title scenarios."""
+ return request.param
+
+
+def test_determine_path_no_path_input(fig_fixture):
+ """Test determine_path with no path input uses current path."""
+ fig_dict, expected_prefix = fig_fixture
+ result = path_tools.determine_path(None, fig_dict, "ext")
+
+ # Should use current directory
+ assert result.parent.resolve() == Path().cwd().resolve()
+ assert result.parent.is_dir()
+
+ assert result.name == f"{expected_prefix}.ext"
+
+
+def test_determine_path_no_suffix_directory(tmp_path, fig_fixture):
+ """Test determine_path with path to directory having no suffix."""
+ fig_dict, expected_prefix = fig_fixture
+
+ # Test directory no suffix
+ test_dir = tmp_path
+ result = path_tools.determine_path(test_dir, fig_dict, "ext")
+
+ # Should use provided directory
+ assert result.parent == test_dir
+ assert result.name == f"{expected_prefix}.ext"
+
+ # Test error
+ nonexistent_dir = Path("/nonexistent/directory")
+ with pytest.raises(ValueError, match=r"Directory .* not found. Please create it."):
+ path_tools.determine_path(nonexistent_dir, fig_dict, "ext")
+
+
+def test_determine_path_directory_with_suffix(tmp_path, fig_fixture):
+ """Test determine_path with path that is directory even with suffix."""
+ fig_dict, expected_prefix = fig_fixture
+
+ # Create a directory with a suffix-like name
+ dir_with_suffix = tmp_path / "mydir.png"
+ dir_with_suffix.mkdir()
+
+ result = path_tools.determine_path(dir_with_suffix, fig_dict, "ext")
+
+ # Should treat as directory
+ assert result.parent == dir_with_suffix
+ assert result.name == f"{expected_prefix}.ext"
+
+
+def test_determine_path_file_with_suffix(tmp_path, fig_fixture):
+ """Test determine_path with file path having suffix."""
+ fig_dict, _expected_prefix = fig_fixture
+
+ # Exists
+ file_path = tmp_path / "output.png"
+ result = path_tools.determine_path(file_path, fig_dict, "ext")
+
+ # Should return the exact path provided
+ assert result == file_path
+
+ # Doesn't exist
+ file_path = Path("/nonexistent/directory/output.png")
+ with pytest.raises(
+ RuntimeError,
+ match=r"Cannot reach path .* Are all directories created?",
+ ):
+ path_tools.determine_path(file_path, fig_dict, "ext")
diff --git a/src/py/tests/test_public_api.py b/src/py/tests/test_public_api.py
index 16fc4767..6e0745d2 100644
--- a/src/py/tests/test_public_api.py
+++ b/src/py/tests/test_public_api.py
@@ -12,7 +12,7 @@
def simple_figure(request):
"""Create a simple plotly figure for testing, either as figure or dict."""
# ruff: noqa: PLC0415
- import plotly.express as px
+ import plotly.express as px # type: ignore[import-untyped]
fig = px.line(x=[1, 2, 3, 4], y=[1, 2, 3, 4])
@@ -30,7 +30,12 @@ async def test_async_api_functions(simple_figure, tmp_path):
# Test write_fig and compare with calc_fig output
write_fig_output = tmp_path / "test_write_fig.png"
- await kaleido.write_fig(simple_figure, path=str(write_fig_output))
+ res = await kaleido.write_fig(
+ simple_figure,
+ path=str(write_fig_output),
+ cancel_on_error=True,
+ )
+ assert res is None
with Path(write_fig_output).open("rb") as f: # noqa: ASYNC230
write_fig_bytes = f.read()
@@ -40,7 +45,7 @@ async def test_async_api_functions(simple_figure, tmp_path):
# Test write_fig_from_object and compare with calc_fig output
write_fig_from_object_output = tmp_path / "test_write_fig_from_object.png"
- await kaleido.write_fig_from_object(
+ res = await kaleido.write_fig_from_object(
[
{
"fig": simple_figure,
@@ -48,6 +53,7 @@ async def test_async_api_functions(simple_figure, tmp_path):
},
],
)
+ assert res == ()
with Path(write_fig_from_object_output).open("rb") as f: # noqa: ASYNC230
write_fig_from_object_bytes = f.read()
@@ -61,7 +67,7 @@ async def test_async_api_functions(simple_figure, tmp_path):
assert write_fig_bytes == write_fig_from_object_bytes == calc_result
-async def test_sync_api_functions(simple_figure, tmp_path):
+async def test_sync_api_functions(simple_figure, tmp_path): # noqa: PLR0915
"""Test sync wrappers with cross-validation."""
# Get expected bytes from calc_fig for comparison
expected_bytes = await kaleido.calc_fig(simple_figure)
@@ -88,7 +94,12 @@ async def test_sync_api_functions(simple_figure, tmp_path):
assert calc_result_1 == expected_bytes
# Test write_fig_sync
- kaleido.write_fig_sync(simple_figure, path=str(write_fig_output_1))
+ res = kaleido.write_fig_sync(
+ simple_figure,
+ path=str(write_fig_output_1),
+ cancel_on_error=True,
+ )
+ assert res is None
with Path(write_fig_output_1).open("rb") as f: # noqa: ASYNC230
write_fig_bytes_1 = f.read()
@@ -96,7 +107,7 @@ async def test_sync_api_functions(simple_figure, tmp_path):
assert write_fig_bytes_1.startswith(b"\x89PNG\r\n\x1a\n"), "Not a PNG file"
# Test write_fig_from_object_sync
- kaleido.write_fig_from_object_sync(
+ res = kaleido.write_fig_from_object_sync(
[
{
"fig": simple_figure,
@@ -104,6 +115,7 @@ async def test_sync_api_functions(simple_figure, tmp_path):
},
],
)
+ assert res == ()
with Path(write_fig_from_object_output_1).open("rb") as f: # noqa: ASYNC230
from_object_bytes_1 = f.read()
@@ -140,7 +152,8 @@ async def test_sync_api_functions(simple_figure, tmp_path):
assert calc_result_2 == expected_bytes
# Test write_fig_sync
- kaleido.write_fig_sync(simple_figure, path=str(write_fig_output_2))
+ res = kaleido.write_fig_sync(simple_figure, path=str(write_fig_output_2))
+ assert res == ()
with Path(write_fig_output_2).open("rb") as f: # noqa: ASYNC230
write_fig_bytes_2 = f.read()
@@ -148,14 +161,16 @@ async def test_sync_api_functions(simple_figure, tmp_path):
assert write_fig_bytes_2.startswith(b"\x89PNG\r\n\x1a\n"), "Not a PNG file"
# Test write_fig_from_object_sync
- kaleido.write_fig_from_object_sync(
+ res = kaleido.write_fig_from_object_sync(
[
{
"fig": simple_figure,
"path": write_fig_from_object_output_2,
},
],
+ cancel_on_error=True,
)
+ assert res is None
with Path(write_fig_from_object_output_2).open("rb") as f: # noqa: ASYNC230
from_object_bytes_2 = f.read()
diff --git a/src/py/tests/test_utils.py b/src/py/tests/test_utils.py
new file mode 100644
index 00000000..0c2e5b07
--- /dev/null
+++ b/src/py/tests/test_utils.py
@@ -0,0 +1,57 @@
+from pathlib import Path
+
+import pytest
+
+from kaleido._utils.path_tools import get_path, is_httpish
+
+pytestmark = pytest.mark.asyncio(loop_scope="function")
+
+# ruff: noqa: S108
+
+
+# Test get_path utility function
+async def test_get_path_with_file_uri():
+ """Test get_path function with file:// URIs."""
+ file_uri = "file:///tmp/test.js"
+ path_wrapped = Path(file_uri)
+ result = get_path(file_uri)
+ assert result == Path("/tmp/test.js")
+ assert get_path(path_wrapped) is path_wrapped
+
+
+async def test_get_path_with_regular_path():
+ """Test get_path function with regular file paths."""
+ regular_path = "/tmp/test.js"
+ path_wrapped = Path(regular_path)
+ result = get_path(regular_path)
+ assert result == Path("/tmp/test.js")
+ assert get_path(path_wrapped) is path_wrapped
+
+
+async def test_get_path_with_http_url():
+ """Test get_path function with HTTP URLs."""
+ http_url = "https://example.com/test.js"
+ path_wrapped = Path(http_url)
+ result = get_path(http_url)
+ assert result == Path("https://example.com/test.js")
+ assert get_path(path_wrapped) is path_wrapped
+
+
+# Test is_httpish utility function
+async def test_is_httpish_with_http():
+ """Test is_httpish function with HTTP URLs."""
+ assert is_httpish("http://example.com/test.js") is True
+ assert is_httpish("https://example.com/test.js") is True
+
+
+async def test_is_httpish_with_file_paths():
+ """Test is_httpish function with file paths."""
+ assert is_httpish("/tmp/test.js") is False
+ assert is_httpish("test.js") is False
+ assert is_httpish("file:///tmp/test.js") is False
+
+
+async def test_is_httpish_with_other_schemes():
+ """Test is_httpish function with other URL schemes."""
+ assert is_httpish("ftp://example.com/test.js") is False
+ assert is_httpish("mailto:test@example.com") is False
diff --git a/src/py/tests/win_hang_scripts/open.py b/src/py/tests/win_hang_scripts/open.py
new file mode 100644
index 00000000..3b2a5d72
--- /dev/null
+++ b/src/py/tests/win_hang_scripts/open.py
@@ -0,0 +1,5 @@
+import kaleido
+
+
+def test_closing_no_close():
+ kaleido.start_sync_server()
diff --git a/src/py/tests/win_hang_scripts/open_close.py b/src/py/tests/win_hang_scripts/open_close.py
new file mode 100644
index 00000000..ea9eb918
--- /dev/null
+++ b/src/py/tests/win_hang_scripts/open_close.py
@@ -0,0 +1,6 @@
+import kaleido
+
+
+def test_open_close():
+ kaleido.start_sync_server()
+ kaleido.stop_sync_server()
diff --git a/src/py/uv.lock b/src/py/uv.lock
index a5bf48ff..e528bd2e 100644
--- a/src/py/uv.lock
+++ b/src/py/uv.lock
@@ -294,7 +294,7 @@ dependencies = [
{ name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "param", version = "2.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "pillow", marker = "python_full_version < '3.9'" },
+ { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "pyct", marker = "python_full_version < '3.9'" },
{ name = "requests", version = "2.32.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "scipy", version = "1.10.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
@@ -346,7 +346,7 @@ dependencies = [
{ name = "multipledispatch", marker = "python_full_version >= '3.10'" },
{ name = "numba", version = "0.62.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
- { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "packaging", marker = "python_full_version >= '3.10'" },
{ name = "pandas", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "param", version = "2.2.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
@@ -380,7 +380,7 @@ version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
wheels = [
@@ -424,7 +424,7 @@ wheels = [
[[package]]
name = "hypothesis"
-version = "6.138.3"
+version = "6.139.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -437,9 +437,9 @@ dependencies = [
{ name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
{ name = "sortedcontainers", marker = "python_full_version >= '3.9'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/19/28/9aa38d1cf2b00d385926fd44318d2b49948c060969ab29e82e8bb654b16c/hypothesis-6.138.3.tar.gz", hash = "sha256:9bffd1382b99e67c46512dac45ec013bae4b39d3d0ef98f0d87535f06d8efc9e", size = 463165, upload-time = "2025-08-24T07:29:16.34Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/56/8e/f408b1a6d9745bf02c3d56e0788c930add554eee6b88a39bba141e897ac4/hypothesis-6.139.2.tar.gz", hash = "sha256:2dc2ff36ea977a9cb7fb68f24a5dbf5d673b88a2e502212676eafe09b699f511", size = 466099, upload-time = "2025-09-18T03:29:15.855Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/20/60/7db4d683d27b24a6dfd8f82ef28332d20b0d99a976ae696569622383c900/hypothesis-6.138.3-py3-none-any.whl", hash = "sha256:19291d3ba478527155c34704b038a21ba86b2f31d36673446f981a67f705b3f4", size = 530081, upload-time = "2025-08-24T07:29:12.862Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/81/4a85771072ae39064f114f23716e312771a42bfe3a089cba3da6697dd231/hypothesis-6.139.2-py3-none-any.whl", hash = "sha256:6f466780b7d1db074fb473af14e3111a5dd4fe36c47fcd776cd7c480ae0a02f2", size = 533752, upload-time = "2025-09-18T03:29:12.088Z" },
]
[[package]]
@@ -479,7 +479,7 @@ dependencies = [
{ name = "choreographer" },
{ name = "logistro" },
{ name = "orjson", version = "3.10.15", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "orjson", version = "3.11.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "orjson", version = "3.11.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "packaging" },
{ name = "pytest-timeout" },
]
@@ -488,30 +488,32 @@ dependencies = [
dev = [
{ name = "async-timeout" },
{ name = "hypothesis", version = "6.113.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "hypothesis", version = "6.138.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "hypothesis", version = "6.139.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "mypy", version = "1.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "mypy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "mypy", version = "1.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "pandas", version = "2.0.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "pandas", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "plotly", extra = ["express"] },
{ name = "poethepoet", version = "0.30.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "poethepoet", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "pyright" },
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "pytest-asyncio", version = "0.24.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "pytest-asyncio", version = "1.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "pytest-asyncio", version = "1.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "pytest-order" },
{ name = "pytest-xdist", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "pytest-xdist", version = "3.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
pickles = [
{ name = "colorcet" },
{ name = "datashader", version = "0.15.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "datashader", version = "0.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "datashader", version = "0.18.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
- { name = "pillow" },
+ { name = "pillow", version = "10.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
+ { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "plotly", extra = ["express"] },
{ name = "zstandard", version = "0.23.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "zstandard", version = "0.25.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
@@ -534,6 +536,7 @@ dev = [
{ name = "pandas", specifier = ">=2.0.3" },
{ name = "plotly", extras = ["express"], specifier = ">=6.1.1" },
{ name = "poethepoet", specifier = ">=0.30.0" },
+ { name = "pyright", specifier = ">=1.1.406" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-order", specifier = ">=1.3.0" },
@@ -728,7 +731,7 @@ wheels = [
[[package]]
name = "mypy"
-version = "1.17.1"
+version = "1.18.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -740,47 +743,47 @@ dependencies = [
{ name = "mypy-extensions", marker = "python_full_version >= '3.9'" },
{ name = "pathspec", marker = "python_full_version >= '3.9'" },
{ name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
- { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/77/a9/3d7aa83955617cdf02f94e50aab5c830d205cfa4320cf124ff64acce3a8e/mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972", size = 11003299, upload-time = "2025-07-31T07:54:06.425Z" },
- { url = "https://files.pythonhosted.org/packages/83/e8/72e62ff837dd5caaac2b4a5c07ce769c8e808a00a65e5d8f94ea9c6f20ab/mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7", size = 10125451, upload-time = "2025-07-31T07:53:52.974Z" },
- { url = "https://files.pythonhosted.org/packages/7d/10/f3f3543f6448db11881776f26a0ed079865926b0c841818ee22de2c6bbab/mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df", size = 11916211, upload-time = "2025-07-31T07:53:18.879Z" },
- { url = "https://files.pythonhosted.org/packages/06/bf/63e83ed551282d67bb3f7fea2cd5561b08d2bb6eb287c096539feb5ddbc5/mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390", size = 12652687, upload-time = "2025-07-31T07:53:30.544Z" },
- { url = "https://files.pythonhosted.org/packages/69/66/68f2eeef11facf597143e85b694a161868b3b006a5fbad50e09ea117ef24/mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94", size = 12896322, upload-time = "2025-07-31T07:53:50.74Z" },
- { url = "https://files.pythonhosted.org/packages/a3/87/8e3e9c2c8bd0d7e071a89c71be28ad088aaecbadf0454f46a540bda7bca6/mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b", size = 9507962, upload-time = "2025-07-31T07:53:08.431Z" },
- { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" },
- { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" },
- { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" },
- { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" },
- { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" },
- { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" },
- { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" },
- { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" },
- { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" },
- { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" },
- { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" },
- { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" },
- { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" },
- { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" },
- { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" },
- { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" },
- { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" },
- { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" },
- { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" },
- { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" },
- { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" },
- { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" },
- { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" },
- { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" },
- { url = "https://files.pythonhosted.org/packages/29/cb/673e3d34e5d8de60b3a61f44f80150a738bff568cd6b7efb55742a605e98/mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9", size = 10992466, upload-time = "2025-07-31T07:53:57.574Z" },
- { url = "https://files.pythonhosted.org/packages/0c/d0/fe1895836eea3a33ab801561987a10569df92f2d3d4715abf2cfeaa29cb2/mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99", size = 10117638, upload-time = "2025-07-31T07:53:34.256Z" },
- { url = "https://files.pythonhosted.org/packages/97/f3/514aa5532303aafb95b9ca400a31054a2bd9489de166558c2baaeea9c522/mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8", size = 11915673, upload-time = "2025-07-31T07:52:59.361Z" },
- { url = "https://files.pythonhosted.org/packages/ab/c3/c0805f0edec96fe8e2c048b03769a6291523d509be8ee7f56ae922fa3882/mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8", size = 12649022, upload-time = "2025-07-31T07:53:45.92Z" },
- { url = "https://files.pythonhosted.org/packages/45/3e/d646b5a298ada21a8512fa7e5531f664535a495efa672601702398cea2b4/mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259", size = 12895536, upload-time = "2025-07-31T07:53:06.17Z" },
- { url = "https://files.pythonhosted.org/packages/14/55/e13d0dcd276975927d1f4e9e2ec4fd409e199f01bdc671717e673cc63a22/mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d", size = 9512564, upload-time = "2025-07-31T07:53:12.346Z" },
- { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" },
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/6f/657961a0743cff32e6c0611b63ff1c1970a0b482ace35b069203bf705187/mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c", size = 12807973, upload-time = "2025-09-19T00:10:35.282Z" },
+ { url = "https://files.pythonhosted.org/packages/10/e9/420822d4f661f13ca8900f5fa239b40ee3be8b62b32f3357df9a3045a08b/mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e", size = 11896527, upload-time = "2025-09-19T00:10:55.791Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/73/a05b2bbaa7005f4642fcfe40fb73f2b4fb6bb44229bd585b5878e9a87ef8/mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b", size = 12507004, upload-time = "2025-09-19T00:11:05.411Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/01/f6e4b9f0d031c11ccbd6f17da26564f3a0f3c4155af344006434b0a05a9d/mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66", size = 13245947, upload-time = "2025-09-19T00:10:46.923Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/97/19727e7499bfa1ae0773d06afd30ac66a58ed7437d940c70548634b24185/mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428", size = 13499217, upload-time = "2025-09-19T00:09:39.472Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/4f/90dc8c15c1441bf31cf0f9918bb077e452618708199e530f4cbd5cede6ff/mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed", size = 9766753, upload-time = "2025-09-19T00:10:49.161Z" },
+ { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" },
+ { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" },
+ { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" },
+ { url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" },
+ { url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" },
+ { url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" },
+ { url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" },
+ { url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" },
+ { url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/a6/490ff491d8ecddf8ab91762d4f67635040202f76a44171420bcbe38ceee5/mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b", size = 12807230, upload-time = "2025-09-19T00:09:49.471Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/2e/60076fc829645d167ece9e80db9e8375648d210dab44cc98beb5b322a826/mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133", size = 11895666, upload-time = "2025-09-19T00:10:53.678Z" },
+ { url = "https://files.pythonhosted.org/packages/97/4a/1e2880a2a5dda4dc8d9ecd1a7e7606bc0b0e14813637eeda40c38624e037/mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6", size = 12499608, upload-time = "2025-09-19T00:09:36.204Z" },
+ { url = "https://files.pythonhosted.org/packages/00/81/a117f1b73a3015b076b20246b1f341c34a578ebd9662848c6b80ad5c4138/mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac", size = 13244551, upload-time = "2025-09-19T00:10:17.531Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/61/b9f48e1714ce87c7bf0358eb93f60663740ebb08f9ea886ffc670cea7933/mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b", size = 13491552, upload-time = "2025-09-19T00:10:13.753Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/66/b2c0af3b684fa80d1b27501a8bdd3d2daa467ea3992a8aa612f5ca17c2db/mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0", size = 9765635, upload-time = "2025-09-19T00:10:30.993Z" },
+ { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" },
]
[[package]]
@@ -806,7 +809,7 @@ wheels = [
[[package]]
name = "narwhals"
-version = "2.1.2"
+version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -814,9 +817,18 @@ resolution-markers = [
"python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/37/f0/b0550d9b84759f4d045fd43da2f811e8b23dc2001e38c3254456da7f3adb/narwhals-2.1.2.tar.gz", hash = "sha256:afb9597e76d5b38c2c4b7c37d27a2418b8cc8049a66b8a5aca9581c92ae8f8bf", size = 533772, upload-time = "2025-08-15T08:24:50.916Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/b8/3cb005704866f1cc19e8d6b15d0467255821ba12d82f20ea15912672e54c/narwhals-2.5.0.tar.gz", hash = "sha256:8ae0b6f39597f14c0dc52afc98949d6f8be89b5af402d2d98101d2f7d3561418", size = 558573, upload-time = "2025-09-12T10:04:24.436Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f8/5a/22741c5c0e5f6e8050242bfc2052ba68bc94b1735ed5bca35404d136d6ec/narwhals-2.5.0-py3-none-any.whl", hash = "sha256:7e213f9ca7db3f8bf6f7eff35eaee6a1cf80902997e1b78d49b7755775d8f423", size = 407296, upload-time = "2025-09-12T10:04:22.524Z" },
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/a8/01/824fff6789ce92a53242d24b6f5f3a982df2f610c51020f934bf878d2a99/narwhals-2.1.2-py3-none-any.whl", hash = "sha256:136b2f533a4eb3245c54254f137c5d14cef5c4668cff67dc6e911a602acd3547", size = 392064, upload-time = "2025-08-15T08:24:48.788Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
]
[[package]]
@@ -902,7 +914,7 @@ resolution-markers = [
dependencies = [
{ name = "llvmlite", version = "0.45.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
- { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e5/96/66dae7911cb331e99bf9afe35703317d8da0fad81ff49fed77f4855e4b60/numba-0.62.0.tar.gz", hash = "sha256:2afcc7899dc93fefecbb274a19c592170bc2dbfae02b00f83e305332a9857a5a", size = 2749680, upload-time = "2025-09-18T17:58:11.394Z" }
wheels = [
@@ -1088,87 +1100,87 @@ wheels = [
[[package]]
name = "numpy"
-version = "2.3.2"
+version = "2.3.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
"python_full_version == '3.11.*'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" },
- { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" },
- { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" },
- { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" },
- { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" },
- { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" },
- { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" },
- { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" },
- { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" },
- { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" },
- { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" },
- { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" },
- { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" },
- { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" },
- { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" },
- { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" },
- { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" },
- { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" },
- { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" },
- { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" },
- { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" },
- { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" },
- { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" },
- { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" },
- { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" },
- { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" },
- { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" },
- { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" },
- { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" },
- { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" },
- { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" },
- { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" },
- { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" },
- { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" },
- { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" },
- { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" },
- { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" },
- { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" },
- { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" },
- { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" },
- { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" },
- { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" },
- { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" },
- { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" },
- { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" },
- { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" },
- { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" },
- { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" },
- { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" },
- { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" },
- { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" },
- { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" },
- { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" },
- { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" },
- { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" },
- { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" },
- { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" },
- { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" },
- { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" },
- { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" },
- { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" },
- { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" },
- { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" },
- { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" },
- { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" },
- { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" },
- { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" },
- { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" },
- { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" },
- { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" },
- { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" },
- { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" },
- { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" },
+ { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" },
+ { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" },
+ { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" },
+ { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" },
+ { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" },
+ { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" },
+ { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" },
+ { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" },
+ { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" },
+ { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" },
+ { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" },
+ { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" },
+ { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" },
+ { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" },
+ { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" },
+ { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" },
+ { url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" },
+ { url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" },
+ { url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" },
+ { url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" },
+ { url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" },
+ { url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" },
+ { url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" },
+ { url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" },
+ { url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" },
+ { url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" },
+ { url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" },
+ { url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" },
+ { url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" },
+ { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" },
+ { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" },
+ { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" },
]
[[package]]
@@ -1262,7 +1274,7 @@ wheels = [
[[package]]
name = "orjson"
-version = "3.11.2"
+version = "3.11.3"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -1270,90 +1282,90 @@ resolution-markers = [
"python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/df/1d/5e0ae38788bdf0721326695e65fdf41405ed535f633eb0df0f06f57552fa/orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309", size = 5470739, upload-time = "2025-08-12T15:12:28.626Z" }
-wheels = [
- { url = "https://files.pythonhosted.org/packages/a1/7b/7aebe925c6b1c46c8606a960fe1d6b681fccd4aaf3f37cd647c3309d6582/orjson-3.11.2-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d6b8a78c33496230a60dc9487118c284c15ebdf6724386057239641e1eb69761", size = 226896, upload-time = "2025-08-12T15:10:22.02Z" },
- { url = "https://files.pythonhosted.org/packages/7d/39/c952c9b0d51063e808117dd1e53668a2e4325cc63cfe7df453d853ee8680/orjson-3.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc04036eeae11ad4180d1f7b5faddb5dab1dee49ecd147cd431523869514873b", size = 111845, upload-time = "2025-08-12T15:10:24.963Z" },
- { url = "https://files.pythonhosted.org/packages/f5/dc/90b7f29be38745eeacc30903b693f29fcc1097db0c2a19a71ffb3e9f2a5f/orjson-3.11.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c04325839c5754c253ff301cee8aaed7442d974860a44447bb3be785c411c27", size = 116395, upload-time = "2025-08-12T15:10:26.314Z" },
- { url = "https://files.pythonhosted.org/packages/10/c2/fe84ba63164c22932b8d59b8810e2e58590105293a259e6dd1bfaf3422c9/orjson-3.11.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32769e04cd7fdc4a59854376211145a1bbbc0aea5e9d6c9755d3d3c301d7c0df", size = 118768, upload-time = "2025-08-12T15:10:27.605Z" },
- { url = "https://files.pythonhosted.org/packages/a9/ce/d9748ec69b1a4c29b8e2bab8233e8c41c583c69f515b373f1fb00247d8c9/orjson-3.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ff285d14917ea1408a821786e3677c5261fa6095277410409c694b8e7720ae0", size = 120887, upload-time = "2025-08-12T15:10:29.153Z" },
- { url = "https://files.pythonhosted.org/packages/c1/66/b90fac8e4a76e83f981912d7f9524d402b31f6c1b8bff3e498aa321c326c/orjson-3.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2662f908114864b63ff75ffe6ffacf996418dd6cc25e02a72ad4bda81b1ec45a", size = 123650, upload-time = "2025-08-12T15:10:30.602Z" },
- { url = "https://files.pythonhosted.org/packages/33/81/56143898d1689c7f915ac67703efb97e8f2f8d5805ce8c2c3fd0f2bb6e3d/orjson-3.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab463cf5d08ad6623a4dac1badd20e88a5eb4b840050c4812c782e3149fe2334", size = 121287, upload-time = "2025-08-12T15:10:31.868Z" },
- { url = "https://files.pythonhosted.org/packages/80/de/f9c6d00c127be766a3739d0d85b52a7c941e437d8dd4d573e03e98d0f89c/orjson-3.11.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:64414241bde943cbf3c00d45fcb5223dca6d9210148ba984aae6b5d63294502b", size = 119637, upload-time = "2025-08-12T15:10:33.078Z" },
- { url = "https://files.pythonhosted.org/packages/67/4c/ab70c7627022d395c1b4eb5badf6196b7144e82b46a3a17ed2354f9e592d/orjson-3.11.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:7773e71c0ae8c9660192ff144a3d69df89725325e3d0b6a6bb2c50e5ebaf9b84", size = 392478, upload-time = "2025-08-12T15:10:34.669Z" },
- { url = "https://files.pythonhosted.org/packages/77/91/d890b873b69311db4fae2624c5603c437df9c857fb061e97706dac550a77/orjson-3.11.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:652ca14e283b13ece35bf3a86503c25592f294dbcfc5bb91b20a9c9a62a3d4be", size = 134343, upload-time = "2025-08-12T15:10:35.978Z" },
- { url = "https://files.pythonhosted.org/packages/47/16/1aa248541b4830274a079c4aeb2aa5d1ff17c3f013b1d0d8d16d0848f3de/orjson-3.11.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:26e99e98df8990ecfe3772bbdd7361f602149715c2cbc82e61af89bfad9528a4", size = 123887, upload-time = "2025-08-12T15:10:37.601Z" },
- { url = "https://files.pythonhosted.org/packages/95/e4/7419833c55ac8b5f385d00c02685a260da1f391e900fc5c3e0b797e0d506/orjson-3.11.2-cp310-cp310-win32.whl", hash = "sha256:5814313b3e75a2be7fe6c7958201c16c4560e21a813dbad25920752cecd6ad66", size = 124560, upload-time = "2025-08-12T15:10:38.966Z" },
- { url = "https://files.pythonhosted.org/packages/74/f8/27ca7ef3e194c462af32ce1883187f5ec483650c559166f0de59c4c2c5f0/orjson-3.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:dc471ce2225ab4c42ca672f70600d46a8b8e28e8d4e536088c1ccdb1d22b35ce", size = 119700, upload-time = "2025-08-12T15:10:40.911Z" },
- { url = "https://files.pythonhosted.org/packages/78/7d/e295df1ac9920cbb19fb4c1afa800e86f175cb657143aa422337270a4782/orjson-3.11.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:888b64ef7eaeeff63f773881929434a5834a6a140a63ad45183d59287f07fc6a", size = 226502, upload-time = "2025-08-12T15:10:42.284Z" },
- { url = "https://files.pythonhosted.org/packages/65/21/ffb0f10ea04caf418fb4e7ad1fda4b9ab3179df9d7a33b69420f191aadd5/orjson-3.11.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:83387cc8b26c9fa0ae34d1ea8861a7ae6cff8fb3e346ab53e987d085315a728e", size = 115999, upload-time = "2025-08-12T15:10:43.738Z" },
- { url = "https://files.pythonhosted.org/packages/90/d5/8da1e252ac3353d92e6f754ee0c85027c8a2cda90b6899da2be0df3ef83d/orjson-3.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e35f003692c216d7ee901b6b916b5734d6fc4180fcaa44c52081f974c08e17", size = 111563, upload-time = "2025-08-12T15:10:45.301Z" },
- { url = "https://files.pythonhosted.org/packages/4f/81/baabc32e52c570b0e4e1044b1bd2ccbec965e0de3ba2c13082255efa2006/orjson-3.11.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a0a4c29ae90b11d0c00bcc31533854d89f77bde2649ec602f512a7e16e00640", size = 116222, upload-time = "2025-08-12T15:10:46.92Z" },
- { url = "https://files.pythonhosted.org/packages/8d/b7/da2ad55ad80b49b560dce894c961477d0e76811ee6e614b301de9f2f8728/orjson-3.11.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:585d712b1880f68370108bc5534a257b561672d1592fae54938738fe7f6f1e33", size = 118594, upload-time = "2025-08-12T15:10:48.488Z" },
- { url = "https://files.pythonhosted.org/packages/61/be/014f7eab51449f3c894aa9bbda2707b5340c85650cb7d0db4ec9ae280501/orjson-3.11.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d08e342a7143f8a7c11f1c4033efe81acbd3c98c68ba1b26b96080396019701f", size = 120700, upload-time = "2025-08-12T15:10:49.811Z" },
- { url = "https://files.pythonhosted.org/packages/cf/ae/c217903a30c51341868e2d8c318c59a8413baa35af54d7845071c8ccd6fe/orjson-3.11.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c0f84fc50398773a702732c87cd622737bf11c0721e6db3041ac7802a686fb", size = 123433, upload-time = "2025-08-12T15:10:51.06Z" },
- { url = "https://files.pythonhosted.org/packages/57/c2/b3c346f78b1ff2da310dd300cb0f5d32167f872b4d3bb1ad122c889d97b0/orjson-3.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:140f84e3c8d4c142575898c91e3981000afebf0333df753a90b3435d349a5fe5", size = 121061, upload-time = "2025-08-12T15:10:52.381Z" },
- { url = "https://files.pythonhosted.org/packages/00/c8/c97798f6010327ffc75ad21dd6bca11ea2067d1910777e798c2849f1c68f/orjson-3.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96304a2b7235e0f3f2d9363ddccdbfb027d27338722fe469fe656832a017602e", size = 119410, upload-time = "2025-08-12T15:10:53.692Z" },
- { url = "https://files.pythonhosted.org/packages/37/fd/df720f7c0e35694617b7f95598b11a2cb0374661d8389703bea17217da53/orjson-3.11.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d7612bb227d5d9582f1f50a60bd55c64618fc22c4a32825d233a4f2771a428a", size = 392294, upload-time = "2025-08-12T15:10:55.079Z" },
- { url = "https://files.pythonhosted.org/packages/ba/52/0120d18f60ab0fe47531d520372b528a45c9a25dcab500f450374421881c/orjson-3.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a134587d18fe493befc2defffef2a8d27cfcada5696cb7234de54a21903ae89a", size = 134134, upload-time = "2025-08-12T15:10:56.568Z" },
- { url = "https://files.pythonhosted.org/packages/ec/10/1f967671966598366de42f07e92b0fc694ffc66eafa4b74131aeca84915f/orjson-3.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b84455e60c4bc12c1e4cbaa5cfc1acdc7775a9da9cec040e17232f4b05458bd", size = 123745, upload-time = "2025-08-12T15:10:57.907Z" },
- { url = "https://files.pythonhosted.org/packages/43/eb/76081238671461cfd0f47e0c24f408ffa66184237d56ef18c33e86abb612/orjson-3.11.2-cp311-cp311-win32.whl", hash = "sha256:f0660efeac223f0731a70884e6914a5f04d613b5ae500744c43f7bf7b78f00f9", size = 124393, upload-time = "2025-08-12T15:10:59.267Z" },
- { url = "https://files.pythonhosted.org/packages/26/76/cc598c1811ba9ba935171267b02e377fc9177489efce525d478a2999d9cc/orjson-3.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:955811c8405251d9e09cbe8606ad8fdef49a451bcf5520095a5ed38c669223d8", size = 119561, upload-time = "2025-08-12T15:11:00.559Z" },
- { url = "https://files.pythonhosted.org/packages/d8/17/c48011750f0489006f7617b0a3cebc8230f36d11a34e7e9aca2085f07792/orjson-3.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:2e4d423a6f838552e3a6d9ec734b729f61f88b1124fd697eab82805ea1a2a97d", size = 114186, upload-time = "2025-08-12T15:11:01.931Z" },
- { url = "https://files.pythonhosted.org/packages/40/02/46054ebe7996a8adee9640dcad7d39d76c2000dc0377efa38e55dc5cbf78/orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486", size = 226528, upload-time = "2025-08-12T15:11:03.317Z" },
- { url = "https://files.pythonhosted.org/packages/e2/c6/6b6f0b4d8aea1137436546b990f71be2cd8bd870aa2f5aa14dba0fcc95dc/orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1", size = 115931, upload-time = "2025-08-12T15:11:04.759Z" },
- { url = "https://files.pythonhosted.org/packages/ae/05/4205cc97c30e82a293dd0d149b1a89b138ebe76afeca66fc129fa2aa4e6a/orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131", size = 111382, upload-time = "2025-08-12T15:11:06.468Z" },
- { url = "https://files.pythonhosted.org/packages/50/c7/b8a951a93caa821f9272a7c917115d825ae2e4e8768f5ddf37968ec9de01/orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c", size = 116271, upload-time = "2025-08-12T15:11:07.845Z" },
- { url = "https://files.pythonhosted.org/packages/17/03/1006c7f8782d5327439e26d9b0ec66500ea7b679d4bbb6b891d2834ab3ee/orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14", size = 119086, upload-time = "2025-08-12T15:11:09.329Z" },
- { url = "https://files.pythonhosted.org/packages/44/61/57d22bc31f36a93878a6f772aea76b2184102c6993dea897656a66d18c74/orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448", size = 120724, upload-time = "2025-08-12T15:11:10.674Z" },
- { url = "https://files.pythonhosted.org/packages/78/a9/4550e96b4c490c83aea697d5347b8f7eb188152cd7b5a38001055ca5b379/orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c", size = 123577, upload-time = "2025-08-12T15:11:12.015Z" },
- { url = "https://files.pythonhosted.org/packages/3a/86/09b8cb3ebd513d708ef0c92d36ac3eebda814c65c72137b0a82d6d688fc4/orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804", size = 121195, upload-time = "2025-08-12T15:11:13.399Z" },
- { url = "https://files.pythonhosted.org/packages/37/68/7b40b39ac2c1c644d4644e706d0de6c9999764341cd85f2a9393cb387661/orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307", size = 119234, upload-time = "2025-08-12T15:11:15.134Z" },
- { url = "https://files.pythonhosted.org/packages/40/7c/bb6e7267cd80c19023d44d8cbc4ea4ed5429fcd4a7eb9950f50305697a28/orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219", size = 392250, upload-time = "2025-08-12T15:11:16.604Z" },
- { url = "https://files.pythonhosted.org/packages/64/f2/6730ace05583dbca7c1b406d59f4266e48cd0d360566e71482420fb849fc/orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45", size = 134572, upload-time = "2025-08-12T15:11:18.205Z" },
- { url = "https://files.pythonhosted.org/packages/96/0f/7d3e03a30d5aac0432882b539a65b8c02cb6dd4221ddb893babf09c424cc/orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e", size = 123869, upload-time = "2025-08-12T15:11:19.554Z" },
- { url = "https://files.pythonhosted.org/packages/45/80/1513265eba6d4a960f078f4b1d2bff94a571ab2d28c6f9835e03dfc65cc6/orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e", size = 124430, upload-time = "2025-08-12T15:11:20.914Z" },
- { url = "https://files.pythonhosted.org/packages/fb/61/eadf057b68a332351eeb3d89a4cc538d14f31cd8b5ec1b31a280426ccca2/orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732", size = 119598, upload-time = "2025-08-12T15:11:22.372Z" },
- { url = "https://files.pythonhosted.org/packages/6b/3f/7f4b783402143d965ab7e9a2fc116fdb887fe53bdce7d3523271cd106098/orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36", size = 114052, upload-time = "2025-08-12T15:11:23.762Z" },
- { url = "https://files.pythonhosted.org/packages/c2/f3/0dd6b4750eb556ae4e2c6a9cb3e219ec642e9c6d95f8ebe5dc9020c67204/orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219", size = 226419, upload-time = "2025-08-12T15:11:25.517Z" },
- { url = "https://files.pythonhosted.org/packages/44/d5/e67f36277f78f2af8a4690e0c54da6b34169812f807fd1b4bfc4dbcf9558/orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad", size = 115803, upload-time = "2025-08-12T15:11:27.357Z" },
- { url = "https://files.pythonhosted.org/packages/24/37/ff8bc86e0dacc48f07c2b6e20852f230bf4435611bab65e3feae2b61f0ae/orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2", size = 111337, upload-time = "2025-08-12T15:11:28.805Z" },
- { url = "https://files.pythonhosted.org/packages/b9/25/37d4d3e8079ea9784ea1625029988e7f4594ce50d4738b0c1e2bf4a9e201/orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe", size = 116222, upload-time = "2025-08-12T15:11:30.18Z" },
- { url = "https://files.pythonhosted.org/packages/b7/32/a63fd9c07fce3b4193dcc1afced5dd4b0f3a24e27556604e9482b32189c9/orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae", size = 119020, upload-time = "2025-08-12T15:11:31.59Z" },
- { url = "https://files.pythonhosted.org/packages/b4/b6/400792b8adc3079a6b5d649264a3224d6342436d9fac9a0ed4abc9dc4596/orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6", size = 120721, upload-time = "2025-08-12T15:11:33.035Z" },
- { url = "https://files.pythonhosted.org/packages/40/f3/31ab8f8c699eb9e65af8907889a0b7fef74c1d2b23832719a35da7bb0c58/orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1", size = 123574, upload-time = "2025-08-12T15:11:34.433Z" },
- { url = "https://files.pythonhosted.org/packages/bd/a6/ce4287c412dff81878f38d06d2c80845709c60012ca8daf861cb064b4574/orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa", size = 121225, upload-time = "2025-08-12T15:11:36.133Z" },
- { url = "https://files.pythonhosted.org/packages/69/b0/7a881b2aef4fed0287d2a4fbb029d01ed84fa52b4a68da82bdee5e50598e/orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e", size = 119201, upload-time = "2025-08-12T15:11:37.642Z" },
- { url = "https://files.pythonhosted.org/packages/cf/98/a325726b37f7512ed6338e5e65035c3c6505f4e628b09a5daf0419f054ea/orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15", size = 392193, upload-time = "2025-08-12T15:11:39.153Z" },
- { url = "https://files.pythonhosted.org/packages/cb/4f/a7194f98b0ce1d28190e0c4caa6d091a3fc8d0107ad2209f75c8ba398984/orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac", size = 134548, upload-time = "2025-08-12T15:11:40.768Z" },
- { url = "https://files.pythonhosted.org/packages/e8/5e/b84caa2986c3f472dc56343ddb0167797a708a8d5c3be043e1e2677b55df/orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8", size = 123798, upload-time = "2025-08-12T15:11:42.164Z" },
- { url = "https://files.pythonhosted.org/packages/9c/5b/e398449080ce6b4c8fcadad57e51fa16f65768e1b142ba90b23ac5d10801/orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5", size = 124402, upload-time = "2025-08-12T15:11:44.036Z" },
- { url = "https://files.pythonhosted.org/packages/b3/66/429e4608e124debfc4790bfc37131f6958e59510ba3b542d5fc163be8e5f/orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d", size = 119498, upload-time = "2025-08-12T15:11:45.864Z" },
- { url = "https://files.pythonhosted.org/packages/7b/04/f8b5f317cce7ad3580a9ad12d7e2df0714dfa8a83328ecddd367af802f5b/orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535", size = 114051, upload-time = "2025-08-12T15:11:47.555Z" },
- { url = "https://files.pythonhosted.org/packages/74/83/2c363022b26c3c25b3708051a19d12f3374739bb81323f05b284392080c0/orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7", size = 226406, upload-time = "2025-08-12T15:11:49.445Z" },
- { url = "https://files.pythonhosted.org/packages/b0/a7/aa3c973de0b33fc93b4bd71691665ffdfeae589ea9d0625584ab10a7d0f5/orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81", size = 115788, upload-time = "2025-08-12T15:11:50.992Z" },
- { url = "https://files.pythonhosted.org/packages/ef/f2/e45f233dfd09fdbb052ec46352363dca3906618e1a2b264959c18f809d0b/orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f", size = 111318, upload-time = "2025-08-12T15:11:52.495Z" },
- { url = "https://files.pythonhosted.org/packages/3e/23/cf5a73c4da6987204cbbf93167f353ff0c5013f7c5e5ef845d4663a366da/orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7", size = 121231, upload-time = "2025-08-12T15:11:53.941Z" },
- { url = "https://files.pythonhosted.org/packages/40/1d/47468a398ae68a60cc21e599144e786e035bb12829cb587299ecebc088f1/orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4", size = 119204, upload-time = "2025-08-12T15:11:55.409Z" },
- { url = "https://files.pythonhosted.org/packages/4d/d9/f99433d89b288b5bc8836bffb32a643f805e673cf840ef8bab6e73ced0d1/orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f", size = 392237, upload-time = "2025-08-12T15:11:57.18Z" },
- { url = "https://files.pythonhosted.org/packages/d4/dc/1b9d80d40cebef603325623405136a29fb7d08c877a728c0943dd066c29a/orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7", size = 134578, upload-time = "2025-08-12T15:11:58.844Z" },
- { url = "https://files.pythonhosted.org/packages/45/b3/72e7a4c5b6485ef4e83ef6aba7f1dd041002bad3eb5d1d106ca5b0fc02c6/orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6", size = 123799, upload-time = "2025-08-12T15:12:00.352Z" },
- { url = "https://files.pythonhosted.org/packages/c8/3e/a3d76b392e7acf9b34dc277171aad85efd6accc75089bb35b4c614990ea9/orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f", size = 124461, upload-time = "2025-08-12T15:12:01.854Z" },
- { url = "https://files.pythonhosted.org/packages/fb/e3/75c6a596ff8df9e4a5894813ff56695f0a218e6ea99420b4a645c4f7795d/orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8", size = 119494, upload-time = "2025-08-12T15:12:03.337Z" },
- { url = "https://files.pythonhosted.org/packages/5b/3d/9e74742fc261c5ca473c96bb3344d03995869e1dc6402772c60afb97736a/orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67", size = 114046, upload-time = "2025-08-12T15:12:04.87Z" },
- { url = "https://files.pythonhosted.org/packages/4f/08/8ebc6dcac0938376b7e61dff432c33958505ae4c185dda3fa1e6f46ac40b/orjson-3.11.2-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:957f10c7b5bce3d3f2ad577f3b307c784f5dabafcce3b836229c269c11841c86", size = 226498, upload-time = "2025-08-12T15:12:06.51Z" },
- { url = "https://files.pythonhosted.org/packages/ff/74/a97c8e2bc75a27dfeeb1b289645053f1889125447f3b7484a2e34ac55d2a/orjson-3.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a669e31ab8eb466c9142ac7a4be2bb2758ad236a31ef40dcd4cf8774ab40f33", size = 111529, upload-time = "2025-08-12T15:12:08.21Z" },
- { url = "https://files.pythonhosted.org/packages/78/c3/55121b5722a1a4e4610a411866cfeada5314dc498cd42435b590353009d2/orjson-3.11.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:adedf7d887416c51ad49de3c53b111887e0b63db36c6eb9f846a8430952303d8", size = 116213, upload-time = "2025-08-12T15:12:09.776Z" },
- { url = "https://files.pythonhosted.org/packages/54/d3/1c810fa36a749157f1ec68f825b09d5b6958ed5eaf66c7b89bc0f1656517/orjson-3.11.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ad8873979659ad98fc56377b9c5b93eb8059bf01e6412f7abf7dbb3d637a991", size = 118594, upload-time = "2025-08-12T15:12:11.363Z" },
- { url = "https://files.pythonhosted.org/packages/09/9c/052a6619857aba27899246c1ac9e1566fe976dbb48c2d2d177eb269e6d92/orjson-3.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9482ef83b2bf796157566dd2d2742a8a1e377045fe6065fa67acb1cb1d21d9a3", size = 120706, upload-time = "2025-08-12T15:12:13.265Z" },
- { url = "https://files.pythonhosted.org/packages/4b/91/ed0632b8bafa5534d40483ca14f4b7b7e8f27a016f52ff771420b3591574/orjson-3.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73cee7867c1fcbd1cc5b6688b3e13db067f968889242955780123a68b3d03316", size = 123412, upload-time = "2025-08-12T15:12:14.807Z" },
- { url = "https://files.pythonhosted.org/packages/90/3d/058184ae52a2035098939329f8864c5e28c3bbd660f80d4f687f4fd3e629/orjson-3.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:465166773265f3cc25db10199f5d11c81898a309e26a2481acf33ddbec433fda", size = 121011, upload-time = "2025-08-12T15:12:16.352Z" },
- { url = "https://files.pythonhosted.org/packages/57/ab/70e7a2c26a29878ad81ac551f3d11e184efafeed92c2ea15301ac71e2b44/orjson-3.11.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bc000190a7b1d2d8e36cba990b3209a1e15c0efb6c7750e87f8bead01afc0d46", size = 119387, upload-time = "2025-08-12T15:12:17.88Z" },
- { url = "https://files.pythonhosted.org/packages/6f/f1/532be344579590c2faa3d9926ec446e8e030d6d04359a8d6f9b3f4d18283/orjson-3.11.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:df3fdd8efa842ccbb81135d6f58a73512f11dba02ed08d9466261c2e9417af4e", size = 392280, upload-time = "2025-08-12T15:12:20.3Z" },
- { url = "https://files.pythonhosted.org/packages/eb/90/dfb90d82ee7447ba0c5315b1012f36336d34a4b468f5896092926eb2921b/orjson-3.11.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3dacfc621be3079ec69e0d4cb32e3764067726e0ef5a5576428f68b6dc85b4f6", size = 134127, upload-time = "2025-08-12T15:12:22.053Z" },
- { url = "https://files.pythonhosted.org/packages/17/cb/d113d03dfaee4933b0f6e0f3d358886db1468302bb74f1f3c59d9229ce12/orjson-3.11.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9fdff73a029cde5f4a1cf5ec9dbc6acab98c9ddd69f5580c2b3f02ce43ba9f9f", size = 123722, upload-time = "2025-08-12T15:12:23.642Z" },
- { url = "https://files.pythonhosted.org/packages/55/78/a89748f500d7cf909fe0b30093ab87d256c279106048e985269a5530c0a1/orjson-3.11.2-cp39-cp39-win32.whl", hash = "sha256:b1efbdc479c6451138c3733e415b4d0e16526644e54e2f3689f699c4cda303bf", size = 124391, upload-time = "2025-08-12T15:12:25.143Z" },
- { url = "https://files.pythonhosted.org/packages/e8/50/e436f1356650cf96ff62c386dbfeb9ef8dd9cd30c4296103244e7fae2d15/orjson-3.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:c9ec0cc0d4308cad1e38a1ee23b64567e2ff364c2a3fe3d6cbc69cf911c45712", size = 119547, upload-time = "2025-08-12T15:12:26.77Z" },
+sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/64/4a3cef001c6cd9c64256348d4c13a7b09b857e3e1cbb5185917df67d8ced/orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7", size = 238600, upload-time = "2025-08-26T17:44:36.875Z" },
+ { url = "https://files.pythonhosted.org/packages/10/ce/0c8c87f54f79d051485903dc46226c4d3220b691a151769156054df4562b/orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120", size = 123526, upload-time = "2025-08-26T17:44:39.574Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/d0/249497e861f2d438f45b3ab7b7b361484237414945169aa285608f9f7019/orjson-3.11.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467", size = 128075, upload-time = "2025-08-26T17:44:40.672Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/64/00485702f640a0fd56144042a1ea196469f4a3ae93681871564bf74fa996/orjson-3.11.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873", size = 130483, upload-time = "2025-08-26T17:44:41.788Z" },
+ { url = "https://files.pythonhosted.org/packages/64/81/110d68dba3909171bf3f05619ad0cf187b430e64045ae4e0aa7ccfe25b15/orjson-3.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a", size = 132539, upload-time = "2025-08-26T17:44:43.12Z" },
+ { url = "https://files.pythonhosted.org/packages/79/92/dba25c22b0ddfafa1e6516a780a00abac28d49f49e7202eb433a53c3e94e/orjson-3.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b", size = 135390, upload-time = "2025-08-26T17:44:44.199Z" },
+ { url = "https://files.pythonhosted.org/packages/44/1d/ca2230fd55edbd87b58a43a19032d63a4b180389a97520cc62c535b726f9/orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf", size = 132966, upload-time = "2025-08-26T17:44:45.719Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/b9/96bbc8ed3e47e52b487d504bd6861798977445fbc410da6e87e302dc632d/orjson-3.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4", size = 131349, upload-time = "2025-08-26T17:44:46.862Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/3c/418fbd93d94b0df71cddf96b7fe5894d64a5d890b453ac365120daec30f7/orjson-3.11.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc", size = 404087, upload-time = "2025-08-26T17:44:48.079Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/a9/2bfd58817d736c2f63608dec0c34857339d423eeed30099b126562822191/orjson-3.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569", size = 146067, upload-time = "2025-08-26T17:44:49.302Z" },
+ { url = "https://files.pythonhosted.org/packages/33/ba/29023771f334096f564e48d82ed855a0ed3320389d6748a9c949e25be734/orjson-3.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6", size = 135506, upload-time = "2025-08-26T17:44:50.558Z" },
+ { url = "https://files.pythonhosted.org/packages/39/62/b5a1eca83f54cb3aa11a9645b8a22f08d97dbd13f27f83aae7c6666a0a05/orjson-3.11.3-cp310-cp310-win32.whl", hash = "sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc", size = 136352, upload-time = "2025-08-26T17:44:51.698Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/c0/7ebfaa327d9a9ed982adc0d9420dbce9a3fec45b60ab32c6308f731333fa/orjson-3.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770", size = 131539, upload-time = "2025-08-26T17:44:52.974Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/8b/360674cd817faef32e49276187922a946468579fcaf37afdfb6c07046e92/orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f", size = 238238, upload-time = "2025-08-26T17:44:54.214Z" },
+ { url = "https://files.pythonhosted.org/packages/05/3d/5fa9ea4b34c1a13be7d9046ba98d06e6feb1d8853718992954ab59d16625/orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91", size = 127713, upload-time = "2025-08-26T17:44:55.596Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/5f/e18367823925e00b1feec867ff5f040055892fc474bf5f7875649ecfa586/orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904", size = 123241, upload-time = "2025-08-26T17:44:57.185Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/bd/3c66b91c4564759cf9f473251ac1650e446c7ba92a7c0f9f56ed54f9f0e6/orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6", size = 127895, upload-time = "2025-08-26T17:44:58.349Z" },
+ { url = "https://files.pythonhosted.org/packages/82/b5/dc8dcd609db4766e2967a85f63296c59d4722b39503e5b0bf7fd340d387f/orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d", size = 130303, upload-time = "2025-08-26T17:44:59.491Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c2/d58ec5fd1270b2aa44c862171891adc2e1241bd7dab26c8f46eb97c6c6f1/orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038", size = 132366, upload-time = "2025-08-26T17:45:00.654Z" },
+ { url = "https://files.pythonhosted.org/packages/73/87/0ef7e22eb8dd1ef940bfe3b9e441db519e692d62ed1aae365406a16d23d0/orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb", size = 135180, upload-time = "2025-08-26T17:45:02.424Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2", size = 132741, upload-time = "2025-08-26T17:45:03.663Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/0c/4577fd860b6386ffaa56440e792af01c7882b56d2766f55384b5b0e9d39b/orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55", size = 131104, upload-time = "2025-08-26T17:45:04.939Z" },
+ { url = "https://files.pythonhosted.org/packages/66/4b/83e92b2d67e86d1c33f2ea9411742a714a26de63641b082bdbf3d8e481af/orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1", size = 403887, upload-time = "2025-08-26T17:45:06.228Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/e5/9eea6a14e9b5ceb4a271a1fd2e1dec5f2f686755c0fab6673dc6ff3433f4/orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824", size = 145855, upload-time = "2025-08-26T17:45:08.338Z" },
+ { url = "https://files.pythonhosted.org/packages/45/78/8d4f5ad0c80ba9bf8ac4d0fc71f93a7d0dc0844989e645e2074af376c307/orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f", size = 135361, upload-time = "2025-08-26T17:45:09.625Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/5f/16386970370178d7a9b438517ea3d704efcf163d286422bae3b37b88dbb5/orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204", size = 136190, upload-time = "2025-08-26T17:45:10.962Z" },
+ { url = "https://files.pythonhosted.org/packages/09/60/db16c6f7a41dd8ac9fb651f66701ff2aeb499ad9ebc15853a26c7c152448/orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b", size = 131389, upload-time = "2025-08-26T17:45:12.285Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/2a/bb811ad336667041dea9b8565c7c9faf2f59b47eb5ab680315eea612ef2e/orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e", size = 126120, upload-time = "2025-08-26T17:45:13.515Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" },
+ { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" },
+ { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" },
+ { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" },
+ { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" },
+ { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" },
+ { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" },
+ { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" },
+ { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" },
+ { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" },
+ { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" },
+ { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" },
+ { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" },
+ { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" },
+ { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" },
+ { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" },
+ { url = "https://files.pythonhosted.org/packages/99/a6/18d88ccf8e5d8f711310eba9b4f6562f4aa9d594258efdc4dcf8c1550090/orjson-3.11.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:56afaf1e9b02302ba636151cfc49929c1bb66b98794291afd0e5f20fecaf757c", size = 238221, upload-time = "2025-08-26T17:46:18.113Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/18/e210365a17bf984c89db40c8be65da164b4ce6a866a2a0ae1d6407c2630b/orjson-3.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913f629adef31d2d350d41c051ce7e33cf0fd06a5d1cb28d49b1899b23b903aa", size = 123209, upload-time = "2025-08-26T17:46:19.688Z" },
+ { url = "https://files.pythonhosted.org/packages/26/43/6b3f8ec15fa910726ed94bd2e618f86313ad1cae7c3c8c6b9b8a3a161814/orjson-3.11.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0a23b41f8f98b4e61150a03f83e4f0d566880fe53519d445a962929a4d21045", size = 127881, upload-time = "2025-08-26T17:46:21.502Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/ed/f41d2406355ce67efdd4ab504732b27bea37b7dbdab3eb86314fe764f1b9/orjson-3.11.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d721fee37380a44f9d9ce6c701b3960239f4fb3d5ceea7f31cbd43882edaa2f", size = 130306, upload-time = "2025-08-26T17:46:22.914Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/a1/1be02950f92c82e64602d3d284bd76d9fc82a6b92c9ce2a387e57a825a11/orjson-3.11.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73b92a5b69f31b1a58c0c7e31080aeaec49c6e01b9522e71ff38d08f15aa56de", size = 132383, upload-time = "2025-08-26T17:46:24.33Z" },
+ { url = "https://files.pythonhosted.org/packages/39/49/46766ac00c68192b516a15ffc44c2a9789ca3468b8dc8a500422d99bf0dd/orjson-3.11.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2489b241c19582b3f1430cc5d732caefc1aaf378d97e7fb95b9e56bed11725f", size = 135159, upload-time = "2025-08-26T17:46:25.741Z" },
+ { url = "https://files.pythonhosted.org/packages/47/e1/27fd5e7600fdd82996329d48ee56f6e9e9ae4d31eadbc7f93fd2ff0d8214/orjson-3.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5189a5dab8b0312eadaf9d58d3049b6a52c454256493a557405e77a3d67ab7f", size = 132690, upload-time = "2025-08-26T17:46:27.271Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/21/f57ef08799a68c36ef96fe561101afeef735caa80814636b2e18c234e405/orjson-3.11.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9d8787bdfbb65a85ea76d0e96a3b1bed7bf0fbcb16d40408dc1172ad784a49d2", size = 131086, upload-time = "2025-08-26T17:46:33.067Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/84/a3a24306a9dc482e929232c65f5b8c69188136edd6005441d8cc4754f7ea/orjson-3.11.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:8e531abd745f51f8035e207e75e049553a86823d189a51809c078412cefb399a", size = 403884, upload-time = "2025-08-26T17:46:34.55Z" },
+ { url = "https://files.pythonhosted.org/packages/11/98/fdae5b2c28bc358e6868e54c8eca7398c93d6a511f0436b61436ad1b04dc/orjson-3.11.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8ab962931015f170b97a3dd7bd933399c1bae8ed8ad0fb2a7151a5654b6941c7", size = 145837, upload-time = "2025-08-26T17:46:36.46Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/a9/2fe5cd69ed231f3ed88b1ad36a6957e3d2c876eb4b2c6b17b8ae0a6681fc/orjson-3.11.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:124d5ba71fee9c9902c4a7baa9425e663f7f0aecf73d31d54fe3dd357d62c1a7", size = 135325, upload-time = "2025-08-26T17:46:38.03Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/a4/7d4c8aefb45f6c8d7d527d84559a3a7e394b9fd1d424a2b5bcaf75fa68e7/orjson-3.11.3-cp39-cp39-win32.whl", hash = "sha256:22724d80ee5a815a44fc76274bb7ba2e7464f5564aacb6ecddaa9970a83e3225", size = 136184, upload-time = "2025-08-26T17:46:39.542Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/1f/1d6a24d22001e96c0afcf1806b6eabee1109aebd2ef20ec6698f6a6012d7/orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb", size = 131373, upload-time = "2025-08-26T17:46:41.227Z" },
]
[[package]]
@@ -1419,7 +1431,7 @@ resolution-markers = [
dependencies = [
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
- { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "python-dateutil", marker = "python_full_version >= '3.9'" },
{ name = "pytz", marker = "python_full_version >= '3.9'" },
{ name = "tzdata", marker = "python_full_version >= '3.9'" },
@@ -1531,6 +1543,9 @@ wheels = [
name = "pillow"
version = "10.4.0"
source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.9'",
+]
sdist = { url = "https://files.pythonhosted.org/packages/cd/74/ad3d526f3bf7b6d3f408b73fde271ec69dfac8b81341a318ce825f2b3812/pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", size = 46555059, upload-time = "2024-07-01T09:48:43.583Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/69/a31cccd538ca0b5272be2a38347f8839b97a14be104ea08b0db92f749c74/pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", size = 3509271, upload-time = "2024-07-01T09:45:22.07Z" },
@@ -1614,13 +1629,132 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/37/ae/2dbfc38cc4fd14aceea14bc440d5151b21f64c4c3ba3f6f4191610b7ee5d/pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", size = 2554652, upload-time = "2024-07-01T09:48:38.789Z" },
]
+[[package]]
+name = "pillow"
+version = "11.3.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.12'",
+ "python_full_version == '3.11.*'",
+ "python_full_version == '3.10.*'",
+ "python_full_version == '3.9.*'",
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" },
+ { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" },
+ { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" },
+ { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" },
+ { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" },
+ { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" },
+ { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" },
+ { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" },
+ { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" },
+ { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" },
+ { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" },
+ { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" },
+ { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" },
+ { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" },
+ { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" },
+ { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" },
+ { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" },
+ { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" },
+ { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" },
+ { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" },
+ { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" },
+ { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" },
+ { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" },
+ { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" },
+ { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" },
+ { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" },
+ { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" },
+ { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" },
+ { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" },
+ { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" },
+ { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" },
+ { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" },
+ { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" },
+ { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" },
+ { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" },
+]
+
[[package]]
name = "plotly"
version = "6.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "narwhals", version = "1.42.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "narwhals", version = "2.1.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "narwhals", version = "2.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
{ name = "packaging" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a0/64/850de5076f4436410e1ce4f6a69f4313ef6215dfea155f3f6559335cad29/plotly-6.3.0.tar.gz", hash = "sha256:8840a184d18ccae0f9189c2b9a2943923fd5cae7717b723f36eef78f444e5a73", size = 6923926, upload-time = "2025-08-12T20:22:14.127Z" }
@@ -1633,7 +1767,7 @@ express = [
{ name = "numpy", version = "1.24.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
{ name = "numpy", version = "2.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" },
- { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
[[package]]
@@ -1731,6 +1865,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
]
+[[package]]
+name = "pyright"
+version = "1.1.406"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nodeenv" },
+ { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" },
+]
+
[[package]]
name = "pytest"
version = "8.3.5"
@@ -1753,7 +1901,7 @@ wheels = [
[[package]]
name = "pytest"
-version = "8.4.1"
+version = "8.4.2"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -1770,9 +1918,9 @@ dependencies = [
{ name = "pygments", marker = "python_full_version >= '3.9'" },
{ name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
]
[[package]]
@@ -1792,7 +1940,7 @@ wheels = [
[[package]]
name = "pytest-asyncio"
-version = "1.1.0"
+version = "1.2.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -1802,12 +1950,12 @@ resolution-markers = [
]
dependencies = [
{ name = "backports-asyncio-runner", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" },
- { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
- { name = "typing-extensions", version = "4.14.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/4e/51/f8794af39eeb870e87a8c8068642fc07bce0c854d6865d7dd0f2a9d338c2/pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea", size = 46652, upload-time = "2025-07-16T04:29:26.393Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/c7/9d/bf86eddabf8c6c9cb1ea9a869d6873b46f105a5d292d3a6f7071f5b07935/pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf", size = 15157, upload-time = "2025-07-16T04:29:24.929Z" },
+ { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" },
]
[[package]]
@@ -1816,7 +1964,7 @@ version = "1.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1d/66/02ae17461b14a52ce5a29ae2900156b9110d1de34721ccc16ccd79419876/pytest_order-1.3.0.tar.gz", hash = "sha256:51608fec3d3ee9c0adaea94daa124a5c4c1d2bb99b00269f098f414307f23dde", size = 47544, upload-time = "2024-08-22T12:29:54.512Z" }
wheels = [
@@ -1829,7 +1977,7 @@ version = "2.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
- { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" }
wheels = [
@@ -1864,7 +2012,7 @@ resolution-markers = [
]
dependencies = [
{ name = "execnet", marker = "python_full_version >= '3.9'" },
- { name = "pytest", version = "8.4.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
+ { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
wheels = [
@@ -2131,7 +2279,7 @@ resolution-markers = [
"python_full_version == '3.11.*'",
]
dependencies = [
- { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" }
wheels = [
@@ -2364,7 +2512,7 @@ wheels = [
[[package]]
name = "typing-extensions"
-version = "4.14.1"
+version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
resolution-markers = [
"python_full_version >= '3.12'",
@@ -2372,9 +2520,9 @@ resolution-markers = [
"python_full_version == '3.10.*'",
"python_full_version == '3.9.*'",
]
-sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
@@ -2473,7 +2621,7 @@ resolution-markers = [
"python_full_version == '3.11.*'",
]
dependencies = [
- { name = "numpy", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
+ { name = "numpy", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "packaging", marker = "python_full_version >= '3.11'" },
{ name = "pandas", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]