diff --git a/CLAUDE.md b/CLAUDE.md index 3ba1df4..25dbc03 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,55 +12,58 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co The project uses PhiFlow for physics simulations and numerical computing. +> For the narrative — goals, architecture rationale, the two contracts with the DealiiX platform, and how to +> extend the project — see [`docs/ONBOARDING.md`](docs/ONBOARDING.md) (Italian: `docs/ONBOARDING.it.md`). This +> `CLAUDE.md` is the mechanics reference; `README.md` covers setup and commands. + ## Development Commands ### Environment Setup -```bash -# Create and activate virtual environment -uv venv -source .venv/bin/activate # Linux/Mac - -# Install dependencies using uv pip sync (recommended) -uv pip sync requirements.txt -# Or install directly -uv pip install -r requirements.txt -``` +This is a uv project (`pyproject.toml` + `uv.lock`); `requirements.in` / `requirements.txt` are legacy. -### Package Management ```bash -# Add a new package (recommended workflow) -echo "" >> requirements.in -uv pip compile requirements.in -o requirements.txt -uv pip sync requirements.txt - -# Alternative: direct install (must still update requirements.txt) -uv pip install -uv pip freeze > requirements.txt +# Create .venv and install all deps (incl. the dev group) from the lockfile +uv sync + +# Then activate, or prefix commands with `uv run` (e.g. `uv run python main.py`) +source .venv/bin/activate # Linux/Mac ``` -### Running Workflows +### Package Management ```bash -# Execute default workflow (network-from-fe.json) with default modules -python main.py +# Add a runtime dependency (updates pyproject.toml + uv.lock, then syncs) +uv add -# Execute specific workflow file -python main.py path/to/workflow.json +# Add a dev-only dependency +uv add --dev -# Execute with specific modules loaded -python main.py path/to/workflow.json --modules="math" -python main.py path/to/workflow.json --modules="math,string,phiflow" +# Re-resolve the lockfile and sync the environment +uv lock && uv sync +``` -# Generate registry from selected modules -python main.py --generate-registry -python main.py --generate-registry --modules="math" -python main.py --generate-registry --modules="math,string,phiflow" +### Running Workflows -# Generate registry to custom file -python main.py --generate-registry --registry-output="custom-registry.json" --modules="math" +`main.py` is a coral-compatible CLI: a global `-p/--plugin` option (comma-separated modules; empty = all) plus +`register` / `run` subcommands. `-p/--plugin` must precede the subcommand. +```bash +# Run a workflow graph (default file network-from-fe.json, all modules) +python main.py run +python main.py run path/to/workflow.json +python main.py -p "math" run path/to/workflow.json +python main.py -p "math,string,phiflow" run path/to/workflow.json + +# Generate the node registry (writes node_types.json into the cwd) +python main.py register +python main.py -p "math" register +python main.py register --output="custom-registry.json" ``` -**Default module behavior**: When no `--modules` flag is provided, only the `phiflow` module is loaded (optimal for physics simulations). Primitives are always included. +The `coral-py` launcher wraps this for the DealiiX platform: it runs `main.py` inside the uv project while +preserving the caller's cwd (so `register` writes `node_types.json` there). Point the platform's `coralBinaryPath` +at `coral-py` and set `coralPluginPath` to the module list. + +**Default module behavior**: When `-p/--plugin` is omitted, all available modules are loaded. Primitives are always included. **Available modules**: - `math` - Mathematical operations and Calculator class @@ -117,7 +120,7 @@ The system is organized into four main modules: - **`definitions/string_ops.py`**: `StringProcessor` class and `print_result` function - **`definitions/phiflow_defs.py`**: PhiFlow physics simulation wrappers (if available) - **Module loading**: Use `--modules` flag to control which definitions are loaded. Default: `phiflow` only. + **Module loading**: Use the `-p/--plugin` option to control which definitions are loaded (comma-separated). Default when omitted: all modules. `AVAILABLE_MODULES` (in `definitions/__init__.py`) lists them. 2. **registry.py** - Registry generation and type conversion: - `generate_registry()`: Introspects functions and classes to create JSON schema @@ -136,29 +139,35 @@ The system is organized into four main modules: - Calling instance methods via method nodes - Storing results for downstream nodes -4. **main.py** - CLI entry point with argparse interface: - - Accepts `--modules` flag to control which definition modules are loaded - - Passes module list to both `WorkflowExecutor` and `save_registry_to_file()` +4. **main.py** - Coral-compatible CLI entry point (argparse): + - Global `-p/--plugin` option names the definition modules to load (comma-separated; empty = all) + - `register` subcommand → `save_registry_to_file()` (writes `node_types.json` into the cwd) + - `run` subcommand → `WorkflowExecutor(...).execute()` + - Wrapped by the `coral-py` launcher for the DealiiX platform ### Workflow JSON Structure **Network Files** (e.g., `network-from-fe.json`): Located at: `workflow.nodes` and `workflow.edges` -Node types: -- Primitive: `{"value": , "node_type": "primitive", "type": ""}` -- Function: `{"node_type": "function", "method_name": ""}` -- Constructor: `{"node_type": "constructor", "type": ""}` -- Method: `{"node_type": "method", "method_name": "."}` +Nodes are **lean**: each carries only its `type` (plus `value` for primitives); the executor infers +the kind from `type`, so `node_type`/`method_name` are not part of the graph. +- Primitive: `{"type": "", "value": }` +- Function: `{"type": ""}` +- Constructor: `{"type": ""}` +- Method: `{"type": "."}` Edge format: - `{"source": "", "target": "", "source_output": , "target_input": }` - **CRITICAL**: `target_input` determines parameter ordering for function/method calls **Registry Files** (e.g., `registry-py.json`): -- Auto-generated schema describing all available node types +- Auto-generated schema describing all available node types, in the DealiiX platform format +- **Keyed by each node's `type`** (primitives by type name, functions by name, constructors by class + name, methods by `Class.method`) — the editor looks entries up as `registry[type]` - Each entry has: - - `arguments`: Array with `connection_type` ("input"/"output") and `type` + - `type`: the node type string (equals the entry's key) + - `arguments`: Array with `connection_type` ("input"/"output"), `type`, and `name` (empty `[]` for primitives) - `inputs`: List of input indices - `outputs`: List of output indices (or `[-1]` for constructors/primitives) - `node_type`: "primitive", "function", "constructor", or "method" @@ -176,27 +185,28 @@ Edge format: ### Node Execution Model -Each node type follows a specific execution pattern in [executor.py](executor.py): +Each node's kind is inferred from its `type` by `WorkflowExecutor._classify()` (membership in +`PRIMITIVES_MAP` / `FUNCTION_MAP` / `CLASS_MAP`, plus the `Class.method` split), then executed: -**Primitive nodes** (`node_type: "primitive"`): +**Primitive nodes** (`type` in `PRIMITIVES_MAP`): - Extract `value` and `type` from node definition - Convert value using `PRIMITIVES_MAP[type]` (handles string-to-type conversion from JSON) - Store result directly -**Function nodes** (`node_type: "function"`): -- Look up function in `FUNCTION_MAP` using `method_name` +**Function nodes** (`type` in `FUNCTION_MAP`): +- Look up function in `FUNCTION_MAP` using `type` - Collect inputs from incoming edges sorted by `target_input` index - Map inputs to function parameters positionally using `inspect.signature()` - Execute function with kwargs, store result -**Constructor nodes** (`node_type: "constructor"`): +**Constructor nodes** (`type` in `CLASS_MAP`): - Look up class in `CLASS_MAP` using `type` field - Collect constructor inputs from edges (sorted by `target_input`) - Map inputs to `__init__` parameters (excluding `self`) - Instantiate class, store instance -**Method nodes** (`node_type: "method"`): -- Parse fully qualified `method_name` (format: `"ClassName.method_name"`) +**Method nodes** (`type` is `Class.method` with the class in `CLASS_MAP`): +- Parse the fully qualified `type` (format: `"ClassName.method_name"`) - First input (lowest `target_input`) must be instance of `ClassName` - Remaining inputs are method parameters - Use `getattr(instance, method_name)` to get bound method @@ -260,7 +270,7 @@ def get_functions() -> Dict[str, Any]: 4. **Regenerate registry with the appropriate module**: ```bash -python main.py --generate-registry --modules="math" +python main.py -p "math" register ``` 5. The function is now available when the module is loaded @@ -291,18 +301,17 @@ def get_classes() -> Dict[str, Any]: return {} ``` -Then register the module in `definitions/__init__.py`: +Then register the module in `definitions/__init__.py` — add the import and one entry to `_MODULES` +(`build_function_map`/`build_class_map` read from `_MODULES`, so no other edit is needed): ```python from . import math_ops, string_ops, phiflow_defs, primitives, numpy_ops -def build_function_map(include=None, exclude=None): - modules = { - 'math': math_ops, - 'string': string_ops, - 'phiflow': phiflow_defs, - 'numpy': numpy_ops, # Add here - } - # ... rest of implementation +_MODULES = { + 'math': math_ops, + 'string': string_ops, + 'phiflow': phiflow_defs, + 'numpy': numpy_ops, # Add here +} ``` **Why wrappers are needed:** @@ -335,7 +344,7 @@ def get_classes() -> Dict[str, Any]: After updating the module, regenerate the registry: ```bash -python main.py --generate-registry --modules="math" +python main.py -p "math" register ``` **How it works:** diff --git a/README.md b/README.md index 5e52902..1c8aec7 100644 --- a/README.md +++ b/README.md @@ -4,70 +4,80 @@ Coral for python libraries ## Installation ### Prerequisites -- Python 3.6+ -- [uv](https://github.com/astral-sh/uv) installed +- Python 3.12+ +- [uv](https://github.com/astral-sh/uv) installed +- `ffmpeg` installed and on `PATH` (e.g. `apt install ffmpeg` / `brew install ffmpeg`) — required for `.mp4` + export from the PhiFlow scripts and the `phiflow_plot_and_save` workflow node, which call matplotlib's + `anim.save(..., writer='ffmpeg')`. Not needed for `.gif` export. ## Setup -```bash -# Create virtual environment -uv venv - -# Activate virtual environment -source .venv/bin/activate # Linux/Mac -# .venv\Scripts\activate # Windows +This is a [uv project](https://docs.astral.sh/uv/concepts/projects/): dependencies are declared in +`pyproject.toml` and pinned in `uv.lock`. -# Install dependencies without extras -uv pip sync requirements.txt +```bash +# Create .venv and install all dependencies (incl. the dev group) from the lockfile +uv sync ``` -### Managing Dependencies +Then either activate the environment (`source .venv/bin/activate`) or prefix commands with `uv run` +(e.g. `uv run python main.py`). `uv run` auto-syncs the environment against `uv.lock` before running. -#### Install new packages with [uv pip compile](https://docs.astral.sh/uv/pip/compile/) +### Managing Dependencies ```bash -# Add the package to requirements.in - -echo "jax" >> requirements.in +# Add a runtime dependency (updates pyproject.toml + uv.lock, then syncs the env) +uv add -#Recompile requirements.txt -uv pip compile requirements.in -o requirements.txt +# Add a dev-only dependency +uv add --dev -# Sync your environment -uv pip sync requirements.txt +# Re-resolve / update the lockfile and sync the environment +uv lock +uv sync ``` +> `requirements.in` / `requirements.txt` are retained for reference only; `pyproject.toml` + `uv.lock` +> are now the source of truth for dependencies. + ## Usage +`main.py` is a **coral-compatible CLI**: a global `-p/--plugin` option naming the definition modules to load +(comma-separated, e.g. `"math,string"`; empty = all) plus two subcommands — `register` (emit the node registry) +and `run` (execute a workflow). `-p/--plugin` must precede the subcommand. This mirrors the C++ `coral` binary so +the DealiiX platform can drive this backend via the [`coral-py` launcher](#coral-launcher-for-the-dealiix-platform). + +> Run the commands below inside an activated venv, or prefix each with `uv run` (e.g. `uv run python main.py run`). + ### 1. Running a stand-alone Phi-flow simulation ```bash # Run a simulation and then check the mp4 or gif file produced -python one_obstacle.py +python phi_flow/one_obstacle.py ``` ### 2. Running the Workflow Executer -Run with default workflow file (network-from-fe.json): +Use the `run` subcommand. With no graph argument it defaults to `network-from-fe.json`: ```bash -python main.py +python main.py run ``` -Run with a specific workflow file: +Run a specific workflow file: ```bash -python main.py path/to/your/workflow.json +python main.py run path/to/your/workflow.json ``` -Run with specific modules loaded: +Load specific modules with `-p/--plugin` (before the subcommand): ```bash # Load only math operations -python main.py workflow.json --modules="math" +python main.py -p "math" run workflow.json # Load multiple modules -python main.py workflow.json --modules="math,string,phiflow" +python main.py -p "math,string,phiflow" run workflow.json ``` -**Default behavior**: When no `--modules` flag is provided, only the `phiflow` module is loaded (optimal for physics simulations). Primitives are always included. +**Default behavior**: When `-p/--plugin` is omitted, all available modules are loaded. Primitives are always included. **Available modules**: - `phiflow` - PhiFlow physics simulation wrappers (default) @@ -76,23 +86,34 @@ python main.py workflow.json --modules="math,string,phiflow" ### 3. Generating the Workflow Registry File -Generate the default registry file registry-py.json: +Use the `register` subcommand. It writes `node_types.json` into the current directory (the filename the +DealiiX platform probes for): ```bash -python main.py --generate-registry +python main.py register ``` -Generate registry with specific modules: +Generate the registry for specific modules: ```bash -# Generate registry for math operations only -python main.py --generate-registry --modules="math" +# Math operations only +python main.py -p "math" register -# Generate registry for all modules -python main.py --generate-registry --modules="math,string,phiflow" +# Multiple modules +python main.py -p "math,string,phiflow" register ``` -**Generate custom output path for registry file:** +**Custom output filename:** ```bash -python main.py --generate-registry --registry-output="custom_registry.json" --modules="math" +python main.py register --output="custom_registry.json" +``` + +### Coral launcher (for the DealiiX platform) + +`coral-py` runs `main.py` inside this repo's uv project while preserving the caller's working directory, so +`register` writes `node_types.json` into that directory. Point the platform's `coralBinaryPath` at it and set +`coralPluginPath` to the module list: +```bash +./coral-py -p "math" register # writes node_types.json into the current directory +./coral-py -p "math" run workflow.json ``` ### More info about the definitions package @@ -103,6 +124,14 @@ See [README.md](definitions/README.md) in the `definitions` directory for more d python main.py --help ``` +## Development + +Extending or modifying coral-python? Start with [`docs/ONBOARDING.md`](docs/ONBOARDING.md) — the onboarding +guide covering goals, architecture, the two contracts with the DealiiX platform, how to add a library or +change internals, design rationale, and an honest account of strengths and weaknesses. An Italian version is +at [`docs/ONBOARDING.it.md`](docs/ONBOARDING.it.md). (This `README.md` is setup + commands; `CLAUDE.md` is the +AI-assisted-development mechanics reference.) + ## Testing Run All Tests: diff --git a/coral-py b/coral-py new file mode 100755 index 0000000..f400fbe --- /dev/null +++ b/coral-py @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +# Coral-compatible launcher for the Python backend. +# +# Runs main.py inside this repo's uv project while preserving the caller's working directory, so +# `register` writes node_types.json into the cwd the platform passed and a relative `--touch-dir` +# resolves there too. Point the DealiiX platform's coralBinaryPath at this file and set +# coralPluginPath to the module list (e.g. "math,string"). +# +# ./coral-py -p "math" register # writes node_types.json into the current directory +# ./coral-py -p "math" run workflow.json # executes the workflow graph +# +# NOTE: --project (not --directory, and no cd) is what keeps the cwd unchanged. +set -euo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec uv run --quiet --project "$HERE" python "$HERE/main.py" "$@" diff --git a/definitions/README.md b/definitions/README.md index 1dfe6f9..798b82c 100644 --- a/definitions/README.md +++ b/definitions/README.md @@ -56,7 +56,7 @@ To add a new domain-specific module: 1. Create a new file in `definitions/` (e.g., `mymodule.py`) 2. Implement `get_functions()` and `get_classes()` that return dictionaries 3. Import the module in `definitions/__init__.py` -4. Add it to the `modules` dictionary in both `build_function_map()` and `build_class_map()` +4. Add one entry to the `_MODULES` dictionary in `definitions/__init__.py` ### Example Module Template @@ -89,19 +89,18 @@ def get_classes() -> Dict[str, Any]: } ``` -Then update `definitions/__init__.py`: +Then update `definitions/__init__.py` — add the import and a single `_MODULES` entry. `AVAILABLE_MODULES` +and both `build_function_map()` / `build_class_map()` read from `_MODULES`, so no other changes are needed: ```python from . import math_ops, string_ops, phiflow_defs, primitives, mymodule -def build_function_map(include=None, exclude=None): - modules = { - 'math': math_ops, - 'string': string_ops, - 'phiflow': phiflow_defs, - 'mymodule': mymodule, # Add here - } - # ... rest of implementation +_MODULES = { + 'math': math_ops, + 'string': string_ops, + 'phiflow': phiflow_defs, + 'mymodule': mymodule, # Add here +} ``` ## Benefits diff --git a/definitions/__init__.py b/definitions/__init__.py index 38393d2..7a83c85 100644 --- a/definitions/__init__.py +++ b/definitions/__init__.py @@ -28,6 +28,17 @@ # Export PRIMITIVES_MAP for direct access PRIMITIVES_MAP = primitives.PRIMITIVES_MAP +# Registry of loadable definition modules, keyed by the short name used in the CLI's -p/--plugin +# option and the `include`/`exclude` lists below. +_MODULES = { + 'math': math_ops, + 'string': string_ops, + 'phiflow': phiflow_defs, +} + +# Names of all loadable modules; used as the "load everything" default. +AVAILABLE_MODULES = list(_MODULES.keys()) + def build_function_map(include: Optional[List[str]] = None, exclude: Optional[List[str]] = None) -> Dict[str, Any]: """ @@ -51,11 +62,7 @@ def build_function_map(include: Optional[List[str]] = None, exclude: Optional[Li # Load everything except string operations >>> build_function_map(exclude=['string']) """ - modules = { - 'math': math_ops, - 'string': string_ops, - 'phiflow': phiflow_defs, - } + modules = _MODULES # Determine which modules to load if include is None: @@ -98,11 +105,7 @@ def build_class_map(include: Optional[List[str]] = None, exclude: Optional[List[ # Load everything except math classes >>> build_class_map(exclude=['math']) """ - modules = { - 'math': math_ops, - 'string': string_ops, - 'phiflow': phiflow_defs, - } + modules = _MODULES # Determine which modules to load if include is None: @@ -134,4 +137,5 @@ def build_class_map(include: Optional[List[str]] = None, exclude: Optional[List[ 'FUNCTION_MAP', 'CLASS_MAP', 'PRIMITIVES_MAP', + 'AVAILABLE_MODULES', ] diff --git a/docs/ONBOARDING.it.md b/docs/ONBOARDING.it.md new file mode 100644 index 0000000..49f7779 --- /dev/null +++ b/docs/ONBOARDING.it.md @@ -0,0 +1,534 @@ +# coral-python — Panoramica + +Una guida a cosa sia questo progetto, perché esista, com'è costruito e come estenderlo — per due +tipi di pubblico: chi vuole **aggiungere una libreria CFD/scientifica** e chi vuole **migliorarne il +design interno o il suo contratto con la piattaforma DealiiX**. + +Questo documento completa `README.md` (setup + comandi quotidiani) e `CLAUDE.md` (riferimento sui +meccanismi per lo sviluppo assistito da AI). Questo è la *storia*: obiettivi, architettura, motivazioni e un +resoconto onesto di punti di forza e di debolezza. + +*Ultimo aggiornamento: 2026-07-09. Descrive coral-python allo stadio di **MVP locale** — un backend +coral-compatibile che esegue i grafi end-to-end in locale; vedi [Roadmap / lavori rinviati](#roadmap--lavori-rinviati) +per cosa è ancora fuori dallo scope (esecuzione remota/Slurm, stadi di pipeline).* + +## Indice + +- [Obiettivi e contesto](#obiettivi-e-contesto) +- [Architettura](#architettura) +- [Installazione](#installazione) +- [Uso](#uso) +- [Aggiungere una nuova libreria](#aggiungere-una-nuova-libreria-persona-a) +- [Estendere gli interni o i contratti](#estendere-gli-interni-o-i-contratti-persona-b) +- [Motivazioni di design / FAQ](#motivazioni-di-design--faq) +- [Punti di forza e di debolezza](#punti-di-forza-e-di-debolezza) +- [Roadmap / lavori rinviati](#roadmap--lavori-rinviati) + +--- + +## Obiettivi e contesto + +**La piattaforma DealiiX** è un editor visuale a nodi: gli utenti costruiscono un grafo di chiamate a +funzioni, costruttori di classi e chiamate a metodi, poi lo esportano come JSON e lo eseguono su un backend. +Il backend originale è **CORAL**, un motore C++ costruito su deal.II per simulazioni agli elementi finiti. + +**coral-python esiste come caso di prova per la cross-validazione.** Se l'approccio della piattaforma — un +editor visuale a nodi che parla con un backend puramente attraverso un protocollo JSON — è solido, dovrebbe +funzionare anche con un *secondo motore indipendente* costruito con strumenti diversi per un dominio diverso +(Python + [PhiFlow](https://github.com/tum-pbs/PhiFlow) per la simulazione di fluidi, invece di C++ + deal.II +per gli elementi finiti). coral-python è quel secondo motore. + +Per rendere il confronto significativo, coral-python non inventa un proprio protocollo — è +**coral-compatibile**: parla la *stessa* superficie CLI e lo *stesso* schema JSON del binario CORAL in C++. +Dal punto di vista della piattaforma, passare dal backend C++ a coral-python significa cambiare due +impostazioni (il percorso dell'eseguibile e il valore del "plugin") e nient'altro. Vedi +[Architettura](#architettura) per sapere esattamente qual è quel contratto. + +--- + +## Architettura + +### Tre livelli, due consumatori indipendenti + +``` + ┌─────────────────────┐ + │ definitions/ │ the single source of truth: + │ math_ops.py │ Python functions & classes, + │ string_ops.py │ with type hints + │ phiflow_defs.py │ + │ primitives.py │ + └──────────┬───────────┘ + │ + build_function_map() / build_class_map() / PRIMITIVES_MAP + │ + ┌─────────────────┴──────────────────┐ + │ │ + ▼ ▼ + ┌───────────────────┐ ┌───────────────────────┐ + │ registry.py │ │ executor.py │ + │ describes nodes │ │ runs nodes │ + │ (JSON schema) │ │ (executes a graph) │ + └─────────┬──────────┘ └───────────┬───────────┘ + │ │ + ▼ ▼ + node_types.json graph results + (→ platform sidebar) (printed / returned) +``` + +`registry.py` ed `executor.py` **non si importano a vicenda.** Entrambi importano solo da `definitions` +(`from definitions import PRIMITIVES_MAP, build_function_map, build_class_map` — riga identica in entrambi i +file). Questo è voluto: il compito del registry è *descrivere* cosa è chiamabile; il compito dell'executor è +*eseguirlo*. Vedi [Estendere gli interni](#estendere-gli-interni-o-i-contratti-persona-b) per capire perché +questo disaccoppiamento conta. + +### I due contratti + +Tutto ciò che la piattaforma ha bisogno da coral-python si riduce a due contratti: + +**1. Il contratto CLI.** `main.py` espone la stessa superficie del binario `coral` in C++: + +``` +main.py -p register [--output FILE] # write the node registry +main.py -p run [--touch-dir DIR] # execute a graph +``` + +`-p`/`--plugin` è riutilizzato: per coral in C++ è il percorso a un plugin compilato (`.so`); per coral-python +è una **lista separata da virgole di moduli di definizione da caricare** (es. `"math,string"`; vuoto significa +"carica tutto" — vedi `_resolve_modules` in `main.py`). Questa è l'*unica* differenza semantica di cui la +piattaforma deve essere a conoscenza, ed è solo una stringa che essa già passa in modo opaco. Lo script di +avvio `coral-py` incapsula `main.py` così che la piattaforma possa puntare l'impostazione `coralBinaryPath` +direttamente a esso (vedi `README.md` per l'invocazione esatta). + +**2. Il contratto JSON.** Due forme JSON: + +- **Registry** (`node_types.json`, prodotto da `register`) — un dizionario indicizzato per la stringa `type` + di ciascun nodo, una voce per ogni primitivo/funzione/costruttore/metodo. È generato da + `registry.py:generate_registry()`. +- **Grafo** (consumato da `run`) — `{"workflow": {"nodes": {...}, "edges": {...}}, ...}`, dove ogni nodo è + *snello* (lean): solo `{"type": "...", "value": ...}` (primitivi) o `{"type": "..."}` (tutto il resto). + Niente `node_type`, niente `method_name` — l'executor deduce cosa **sia** un nodo puramente dalla sua + stringa `type` (vedi `executor.py:_classify`). Questo combacia esattamente con ciò che la piattaforma + esporta. + +### Il flusso dei dati in pratica + +``` +1. Probe: la piattaforma esegue `coral-py -p "math,string" register` + → registry.py introspeziona math_ops.py + string_ops.py + → scrive node_types.json + → la piattaforma lo legge e popola la barra laterale + +2. Build: l'utente trascina i nodi sul canvas, li collega, + la piattaforma esporta un graph.json snello + +3. Run: la piattaforma esegue `coral-py -p "math,string" run graph.json` + → executor.py carica graph.json, classifica ogni nodo per `type`, + fa l'ordinamento topologico e chiama le vere funzioni/classi Python + → i risultati sono stampati su stdout (catturati come log dell'esecuzione) +``` + +### Come il registry legge le firme — `inspect.signature`, e se convenga tenerlo + +Il registry è interamente **guidato dalle annotazioni**, e il meccanismo che legge quelle annotazioni è +`inspect.signature` della libreria standard. Vale la pena capirlo con precisione, perché è il singolo fatto +che spiega perché la maggior parte delle librerie ha bisogno di un wrapper. + +**Come funziona.** `registry.py` chiama `inspect.signature(...)` su ogni callable — una funzione +(`_add_function_node`), un costruttore (`_add_constructor`, su `cls.__init__`) o un metodo (`_add_methods`). +Poi scorre `sig.parameters` (ordinati, ognuno con la sua `.annotation`) più `sig.return_annotation`, e passa +ogni annotazione attraverso `python_type_to_string`, che la mappa contro il `PRIMITIVES_MAP` di sei voci +(`int`, `float`, `str`, `bool`, `any`, `none`). Da questo derivano due comportamenti: + +- Un'annotazione di **parametro mancante** diventa `"any"` — usabile, solo debolmente tipizzata. +- Un'annotazione di **ritorno mancante** non produce **alcun socket di output** (`_process_return_type` + restituisce `[], []`), quindi il nodo diventa un vicolo cieco. Un ritorno `Tuple[...]`, al contrario, + diventa un socket di output per elemento. + +`executor.py` chiama `inspect.signature` in modo indipendente (quando collega un nodo funzione, costruttore o +metodo) — i due file non condividono mai un helper per le firme, ed è la giuntura "convenzione, non contratto" +descritta in [Estendere gli interni](#estendere-gli-interni-o-i-contratti-persona-b). + +**Perché è stato scelto.** È nella libreria standard (zero dipendenze), e una singola chiamata restituisce +parametri ordinati, default e annotazione di ritorno in una forma uniforme tra funzioni, metodi e costruttori. +Per un registry guidato dalle annotazioni è la cosa minima che funziona, ed è del tutto sufficiente per il +codice che possediamo noi — i nostri wrapper tipizzati e le classi Python pure e annotate come `Calculator` si +registrano senza alcun adattatore. + +**Il suo limite onesto.** `inspect.signature` legge solo le annotazioni *grezze* che esistono sull'oggetto a +runtime. È un confine netto in due direzioni, ed entrambe sono comuni: + +- **Il codice implementato in C non porta annotazioni a runtime.** Tutto in `math`, gran parte di `numpy` e i + percorsi veloci delle librerie scientifiche si introspezionano con parametri *vuoti* e ritorno *vuoto* — + quindi si registrerebbero con input `"any"` e nessun output. +- **Le librerie Python pure moderne trasformano le annotazioni in stringhe.** Con + `from __future__ import annotations` (PEP 563), `inspect.signature` restituisce la *stringa* `"float"` + invece del tipo `float`, e il controllo di identità di `python_type_to_string` contro `PRIMITIVES_MAP` + la manca → `"any"`. + +Abbiamo misurato quanto dell'ecosistema reale questo esclude, e la risposta fa riflettere: su **751 callable +pubblici** in `numpy` (461), `jax` (98) e `phi.flow` (192), **zero** sono direttamente registrabili in un nodo +pulito e collegabile — `numpy` perché è C (nessuna annotazione), `jax` perché usa PEP 563 (77 dei suoi +callable annotati tornano come stringhe), `phi.flow` perché i suoi tipi non sono primitivi. Scansionando ogni +modulo di terze parti di livello superiore installato qui, solo tre esponevano *qualche* callable usabile +nativamente, ed erano helper incidentali (`pyparsing.col`, `opt_einsum.get_symbol`, `iniconfig.iscommentline`). +**La conclusione pratica: i wrapper scritti a mano e annotati coi tipi sono la regola, non un caso limite** — +vedi [perché `math.sqrt` ha bisogno di un wrapper](#perché-mathsqrt-ha-bisogno-di-un-wrapper-non-possiamo-caricare-le-funzioni-python-dinamicamente). + +**Le alternative, e la nostra opinione su ciascuna.** + +- **`typing.get_type_hints()`** — risolve le annotazioni-stringa PEP 563 e i riferimenti in avanti che + `inspect.signature` grezzo lascia come stringhe. È un cambiamento economico e a basso rischio che + sbloccherebbe l'intera classe delle librerie Python pure e moderne annotate (renderebbe, per esempio, + leggibili le firme trasformate in stringhe di `jax`). *La nostra opinione: il primo miglioramento che vale la + pena fare.* Non risolve il codice C (non ci sono comunque annotazioni da risolvere) ed è ancora limitato + alla mappa di sei primitivi, ma elimina il fallimento evitabile più comune. +- **Parsing statico dell'AST dei file sorgente** — estrae le firme senza importare, evitando gli effetti + collaterali dell'import. Macchinario più pesante, e ancora dipendente dalle annotazioni (legge gli stessi + hint). *Non ne vale la pena a questa scala.* +- **Registrazione esplicita con decoratori / schema manuale** — precisa e senza introspezione, ma baratta ogni + firma con boilerplate scritto a mano. *Vale la pena solo se dobbiamo deliberatamente registrare molti + callable non annotabili.* +- **Lettura degli stub `.pyi`** — l'*unica* via che potrebbe recuperare i tipi per le funzioni C (`numpy` ecc.), + dato che quell'informazione vive solo negli stub. Ma è ad alta complessità e fragile (scoperta degli stub, + disallineamenti di versione). *Probabilmente non ne vale la pena; un wrapper scritto a mano è più semplice e + più onesto sulle intenzioni.* + +**In conclusione.** Teniamo `inspect.signature` per ora — è semplice e pienamente sufficiente per il codice che +possediamo. Se in futuro vorremo che più librerie esterne "funzionino e basta", il percorso pragmatico è +`get_type_hints()` più una mappa di tipi più ricca, in quest'ordine. Ma i wrapper restano inevitabili per le +librerie C e di array qualunque lettore scegliamo: è una proprietà dell'ecosistema Python (nessun tipo a +runtime per il codice compilato), non un difetto di questo design. + +### Come l'executor esegue un grafo — ordine di esecuzione e `_classify` + +Due meccanismi in `executor.py` trasformano un grafo snello in risultati. + +**Ordine di esecuzione.** `get_execution_order()` è un ordinamento topologico (algoritmo di Kahn): costruisce +una lista di adiacenza più un conteggio dei gradi entranti a partire dagli archi, inizializza una coda con ogni +nodo a grado entrante zero, e la svuota decrementando man mano i gradi entranti a valle. Se l'ordine emesso è +più corto del numero di nodi c'è un ciclo, e solleva un errore. La garanzia che questo compra: un nodo gira +solo dopo che esistono tutti i suoi input, mentre i rami indipendenti non hanno un ordine relativo definito +(qualsiasi ordine topologico valido va bene). + +**Classificazione dei nodi.** Poiché i nodi snelli portano solo `{type, value?}`, l'executor deve recuperare +cosa sia ciascun nodo prima di poterlo eseguire. `_classify(type_str)` fa esattamente questo, ed è +deliberatamente economico — poche verifiche di appartenenza a hash-map costruite **una sola volta** in +`__init__`, quindi è di fatto **O(1) per nodo** e mai un collo di bottiglia: + +```python +if type_str in self.primitives_map: return "primitive" # O(1) +if type_str in self.function_map: return "function" # O(1) +if type_str in self.class_map: return "constructor" # O(1) +if "." in type_str and type_str.rsplit(".", 1)[0] in self.class_map: + return "method" # one split + O(1) +``` + +Due cose su cui vale la pena essere precisi: + +- `_classify` recupera solo il *tipo* (kind) del nodo, non la forma dei suoi argomenti. I nomi e l'ordine dei + parametri sono ri-derivati al momento della chiamata con `inspect.signature(func | __init__ | method)` — lo + stesso lettore che usa il registry — e gli input vengono poi collegati come kwargs. Quella chiamata a + `inspect.signature` per nodo è economica ma non memorizzata in cache. +- Le uniche parti lievemente non lineari vivono altrove, e nessuna conta alle dimensioni di grafo odierne: + l'ordinamento topologico usa una lista come coda (`queue.pop(0)` è O(n)), e ogni nodo riscansiona l'intera + lista di archi per trovare i suoi archi entranti (`[e for e in self.edges if e["target"] == node_id]`, + O(V·E) complessivo). Sostituire con un `collections.deque` e pre-raggruppare gli archi per target renderebbe + lineare un'intera esecuzione — una vittoria pulita e a basso rischio per la + [Persona B](#estendere-gli-interni-o-i-contratti-persona-b) se i grafi dovessero mai crescere molto. + +--- + +## Installazione + +coral-python è un progetto [uv](https://docs.astral.sh/uv/) (`pyproject.toml` + `uv.lock`): + +```bash +uv sync # creates .venv, installs deps (incl. the dev group) from the lockfile +``` + +Poi attiva il venv (`source .venv/bin/activate`) oppure prefissa i comandi con `uv run`. Vedi `README.md` per +la sezione completa di setup, la gestione delle dipendenze (`uv add`) e l'esecuzione della suite di test. + +--- + +## Uso + +```bash +# Generate the registry for one or more modules (writes node_types.json in the cwd) +uv run python main.py -p "math" register + +# Run a graph with those modules loaded +uv run python main.py -p "math" run tests/fixtures/valid_workflows/network-from-fe-math.json +``` + +Tramite il launcher (ciò che la piattaforma invoca davvero): + +```bash +./coral-py -p "math,string,phiflow" register +./coral-py -p "math,string,phiflow" run graph.json +``` + +`coral-py` esegue `main.py` dentro il `.venv` di questo progetto tramite `uv run --project`, **senza cambiare +la cartella di lavoro** — così l'output di `register` e la cartella di lavoro configurata dalla piattaforma +restano coerenti con ciò che la piattaforma si aspetta (vedi i commenti in `coral-py`). + +Sul lato piattaforma: Impostazioni → Modalità di esecuzione → **Local / Coral**, con il *Coral binary path* +puntato su `coral-py` e il campo *Coral plugin path* che contiene la lista dei moduli (quel campo accetta testo +libero proprio per supportare questo — vedi dealiiX-platform PR #209). Poi **Save & Sync** interroga il +registry, ed **Execute** esegue un grafo. + +--- + +## Aggiungere una nuova libreria (Persona A) + +Vuoi aggiungere il supporto a una libreria CFD/scientifica diversa da PhiFlow — poniamo, un diverso solver di +fluidi, una libreria di mesh o un pacchetto di calcolo numerico. + +### I passi + +1. **Crea `definitions/_ops.py`.** Deve esporre esattamente due funzioni senza argomenti: + + ```python + def get_functions() -> Dict[str, Any]: + return {"my_function": my_function} + + def get_classes() -> Dict[str, Any]: + return {"MyClass": MyClass} + ``` + + È un contratto duck-typed — nulla lo impone tramite una classe base astratta, ma ogni modulo in + `definitions/` lo segue (vedi `math_ops.py`, `string_ops.py`, `phiflow_defs.py`). + +2. **Scrivi funzioni/classi wrapper tipizzate**, non chiamate dirette dentro la libreria. Vedi + [perché il wrapping è necessario](#perché-mathsqrt-ha-bisogno-di-un-wrapper-non-possiamo-caricare-le-funzioni-python-dinamicamente) + più sotto — in breve: il registry può produrre un nodo utile solo se la funzione ha parametri annotati coi + tipi e un valore di ritorno annotato coi tipi. + + ```python + # definitions/mycfd_ops.py + from mycfd import Solver # the real library + + def create_solver(resolution: int) -> Any: + """Wrap Solver's constructor with an explicit, registry-friendly signature.""" + return Solver(resolution=resolution) + + def get_functions() -> Dict[str, Any]: + return {"create_solver": create_solver} + + def get_classes() -> Dict[str, Any]: + return {} + ``` + +3. **Se la libreria potrebbe non essere installata ovunque**, proteggi l'import come fa + `phiflow_defs.py` — prova l'import, imposta un flag `AVAILABLE`, definisci le funzioni/classi wrapper solo + sotto `if AVAILABLE:`, e restituisci `{}` da `get_functions()`/`get_classes()` quando non è disponibile. + Questo mantiene coral-python importabile e gli altri moduli funzionanti anche quando la tua libreria non è + installata. + +4. **Registra il modulo** in `definitions/__init__.py` — aggiungi l'import e una voce a `_MODULES`: + + ```python + from . import math_ops, string_ops, phiflow_defs, primitives, mycfd_ops + + _MODULES = { + 'math': math_ops, + 'string': string_ops, + 'phiflow': phiflow_defs, + 'mycfd': mycfd_ops, # add here + } + ``` + + `AVAILABLE_MODULES` e sia `build_function_map`/`build_class_map` lo raccolgono automaticamente — nessun + altro cambiamento di codice necessario. + +5. **Rigenera e controlla il registry**, poi esegui un grafo: + + ```bash + uv run python main.py -p "mycfd" register --output=/tmp/check.json + # inspect /tmp/check.json — every function/class you exposed should have a sensible + # arguments/inputs/outputs shape, not everything collapsed to "any" + uv run python main.py -p "mycfd" run my_test_graph.json + ``` + +### Perché `math.sqrt` ha bisogno di un wrapper? Non possiamo caricare le funzioni Python dinamicamente? + +Salta fuori subito appena guardi `math_ops.py` — `math.sqrt` non è registrato direttamente; c'è invece un +wrapper `math_sqrt(x: float) -> float` che lo chiama. La ragione è strutturale, non stilistica: + +Il registry (`registry.py:generate_registry`) è **guidato dalle annotazioni**. Per ogni parametro e valore di +ritorno chiama `inspect.signature(func)` e converte l'annotazione in una stringa di tipo del protocollo +tramite `python_type_to_string`: + +```python +def python_type_to_string(py_type) -> str: + # Handle empty/missing annotations + if py_type is inspect.Signature.empty or py_type is None: + return _REVERSE_PRIMITIVES_MAP[Any] + ... +``` + +Un'annotazione mancante diventa `"any"`. Peggio, per i valori di **ritorno**, `_process_return_type` tratta +un'annotazione mancante come *nessun socket di output*: + +```python +if (return_annotation is not None + and return_annotation != type(None) + and return_annotation != inspect.Signature.empty): + return [_create_output_argument(return_annotation)], [param_idx] +return [], [] # <- missing/None annotation → zero outputs +``` + +`math.sqrt` è un builtin C (`builtin_function_or_method`). Anche dove `inspect.signature` ha successo su di +esso, i parametri e il ritorno non portano **alcuna annotazione di tipo** — quell'informazione semplicemente +non esiste a runtime per le funzioni implementate in C; vive solo nei file di stub `.pyi`, che qui nulla legge. +Registrare `math.sqrt` direttamente produrrebbe quindi un nodo con un input `"any"` e **nessun socket di +output** — impossibile da collegare a qualsiasi cosa a valle. + +Il wrapper è la correzione più piccola: fornisce le annotazioni che l'introspezione a runtime di Python non +riesce a recuperare, ed è anche un posto comodo per il logging e la coercizione di tipo (es. riconvertire uno +scalare NumPy in un `float` Python). È un vincolo reale e strutturale — non un ripiego — ogni volta che +incapsuli un'estensione C o una libreria non annotata. + +**Quando non ti serve un wrapper:** se la funzione o la classe è Python puro *e porta già i type hint*, +registrala direttamente — nessun wrapper richiesto. È esattamente ciò che fa `Calculator` in `math_ops.py`: il +suo `__init__` e i suoi metodi sono Python annotato, quindi `registry.py` li introspeziona senza alcun +adattatore. + +--- + +## Estendere gli interni o i contratti (Persona B) + +Vuoi cambiare come coral-python funziona internamente, o far evolvere il suo contratto con la piattaforma. + +### Il disaccoppiamento è reale, ed è il tuo punto di estensione + +Poiché `registry.py` ed `executor.py` non si importano mai a vicenda ed entrambi consumano `definitions` solo +attraverso `build_function_map`/`build_class_map`/`PRIMITIVES_MAP`, puoi riscrivere l'intero livello +`definitions/` — una diversa strategia di introspezione, generazione di codice, un meccanismo di scoperta dei +plugin, qualunque cosa — e sia il generatore del registry sia l'executor continuano a funzionare *invariati*, +purché: + +1. `build_function_map(include=...)` / `build_class_map(include=...)` continuino a restituire dizionari + `{name: callable}` / `{name: class}`, e +2. la forma JSON che ciascun lato produce/consuma resti `{type, arguments, inputs, outputs, node_type}` per le + voci del registry e `{type, value?}` per i nodi di grafo snelli. + +È una giuntura davvero utile: significa che "migliorare il sistema di tipi del registry" e "migliorare come i +nodi vengono scoperti" sono progetti separabili. + +**Il costo di quel disaccoppiamento:** è imposto per *convenzione*, non da un'interfaccia o un test condiviso +che vincoli insieme i due lati. `registry.py` ed `executor.py` codificano **in modo indipendente** le stesse +assunzioni — es. che un nome puntato come `"math.sqrt"` sia una funzione, non un metodo (vedi il commento in +`executor.py:_classify`: *"functions checked before the split so dotted names like `math.sqrt` resolve as +functions, not methods"*), e che l'argomento `self` di un metodo sia sempre l'input all'indice 0. Nulla +controlla che un cambiamento su un lato non rompa silenziosamente le assunzioni dell'altro — se tocchi questo +confine, aggiorna entrambi e riesegui l'intera suite (`uv run pytest`). + +### Punti di estensione concreti + +- **Sistema di tipi più ricco.** Solo i sei tipi di `PRIMITIVES_MAP` (`int`, `float`, `str`, `bool`, `any`, + `none`) fanno il round-trip attraverso il registry; ogni altra annotazione (una classe di dominio, `list`, + un elemento di tupla non primitivo) collassa a `"any"`. Uno schema più ricco (es. registrare i nomi delle + classi di dominio come loro propri tipi del protocollo, come già fanno gli argomenti `self` dei metodi con il + nome della classe) darebbe socket più precisi e una migliore validazione sul canvas. +- **Import pigro dei moduli.** `definitions/__init__.py` importa ogni modulo in `_MODULES` al momento + dell'import del package — incluso `phiflow_defs`, che tenta l'intera catena di import PhiFlow/JAX + indipendentemente dal fatto che `-p` lo abbia selezionato. Importare solo i moduli nominati in `include` + eviterebbe di pagare per dipendenze non usate. +- **Stato di esecuzione per-nodo.** La CLI accetta `--touch-dir` per compatibilità con la funzionalità di + stato per-nodo dal vivo della piattaforma, ma non ci scrive ancora nulla — `executor.py` dovrebbe emettere + un file di stato per nodo man mano che esegue. +- **Imporre la convenzione registry/executor.** Un test condiviso (o un'unica fonte di "come classificare una + stringa `type`") contro cui sia `registry.py` sia `executor.py` vengano verificati rimuoverebbe il rischio + "convenzione, non imposizione" descritto sopra. +- **Esecuzione in tempo lineare.** L'ordinamento topologico dell'executor usa una lista come coda e ogni nodo + riscansiona l'intera lista di archi per i suoi input (vedi [Come l'executor esegue un grafo](#come-lexecutor-esegue-un-grafo--ordine-di-esecuzione-e-_classify)). + Un `collections.deque` più gli archi pre-raggruppati per target rende lineare un'intera esecuzione — non + necessario alle dimensioni odierne, ma una vittoria pulita prima di scalare a grafi grandi. (Nota: + `_classify` di per sé è già O(1) per nodo.) + +--- + +## Motivazioni di design / FAQ + +### Perché `math.sqrt` ha bisogno di un wrapper? Non possiamo caricare le funzioni Python dinamicamente senza wrapping manuale? + +Risposto per esteso [sopra](#perché-mathsqrt-ha-bisogno-di-un-wrapper-non-possiamo-caricare-le-funzioni-python-dinamicamente). +In breve: il registry è guidato dalle annotazioni, e Python non espone annotazioni di tipo a runtime per le +funzioni implementate in C — non c'è nulla da introspezionare. Le funzioni e le classi Python pure *con* type +hint (come `Calculator`) non hanno bisogno di alcun wrapper. + +### Il disaccoppiamento registry/executor permette davvero a qualcuno di riscrivere `definitions` sotto lo stesso contratto? + +Sì — vedi [Estendere gli interni](#il-disaccoppiamento-è-reale-ed-è-il-tuo-punto-di-estensione) sopra. È una +genuina proprietà architetturale (verificato: nessuno dei due moduli importa l'altro; entrambi toccano solo la +superficie pubblica di `definitions`), con un'unica riserva onesta: la separazione è basata sulla convenzione, +non imposta da un contratto, quindi i cambiamenti su un lato richiedono un controllo corrispondente sull'altro. + +### Perché `_MODULES` e le funzioni `build_*_map` sono definite in `definitions/__init__.py` e non altrove? + +È un idioma Python standard: l'`__init__.py` di un package che fa da piccolo **registro di plugin** — aggrega i +moduli fratelli (`math_ops`, `string_ops`, `phiflow_defs`, ...) che soddisfano ciascuno un contratto duck-typed +(`get_functions()`/`get_classes()`), ed espone un paio di funzioni factory (`build_function_map`, +`build_class_map`) come API pubblica del package. È comune e appropriato a questa scala — ricorreresti a +qualcosa di più pesante (entry point di setuptools, un sistema di registrazione basato su decoratori) solo se i +moduli dovessero essere scopribili da *fuori* questo package (es. come plugin di terze parti installabili), che +non è il caso qui. + +Due cose da sapere se lavori in questo file: +- `FUNCTION_MAP`/`CLASS_MAP` sono anche costruite **subito al momento dell'import**, "per retrocompatibilità" + secondo il commento — ma né `registry.py` né `executor.py` le usano davvero; entrambi chiamano + `build_function_map(include=...)`/`build_class_map(include=...)` con una lista esplicita di moduli. Quei due + globali sono vestigiali. +- Le due funzioni `build_*_map` duplicano la stessa logica di risoluzione include/exclude. Inoltre, poiché + entrambe fanno `.update()` in un dizionario condiviso, se due moduli definiscono la stessa chiave (oggi, + `print_result` esiste sia in `math_ops.py` sia in `string_ops.py`) il modulo successivo vince + silenziosamente. Innocuo oggi dato che il duplicato è identico, ma da sapere prima di aggiungere un nome in + conflitto. + +--- + +## Punti di forza e di debolezza + +**Punti di forza** + +- Separazione pulita in tre livelli (`definitions` → `registry`/`executor`) con un disaccoppiamento reale e + verificabile tra descrivere ed eseguire un grafo. +- Genuinamente coral-compatibile: stessa superficie CLI, stesso schema JSON del backend C++ — la piattaforma + non ha bisogno di alcun codice specifico per il backend per pilotarlo. +- Il protocollo di grafo snello e indicizzato per tipo combacia esattamente con il formato di export attuale + della piattaforma (nessun adattatore necessario sul lato piattaforma). +- Gestione elegante delle dipendenze opzionali (la guardia `AVAILABLE` di `phiflow_defs.py`) — il package + resta importabile e gli altri moduli funzionano ancora se PhiFlow non è installato. +- Superficie piccola e ben testata: 88 test che passano coprendo la generazione del registry, l'esecuzione e il + caricamento dei moduli. + +**Punti di debolezza** + +- **Sistema di tipi con perdita** — solo sei tipi primitivi fanno il round-trip attraverso il registry; tutto + il resto diventa `"any"`, il che indebolisce la validazione delle connessioni sul canvas. +- **Asimmetria delle annotazioni** — un'annotazione di parametro mancante diventa `"any"` (ancora usabile), ma + un'annotazione di ritorno mancante non produce *alcun socket di output* (il nodo diventa un vicolo cieco). + Facile inciamparci quando si scrive un nuovo wrapper. +- **I metodi delle estensioni C vengono scartati silenziosamente.** Il controllo `inspect.isfunction` di + `_add_methods` filtra via i metodi delle classi implementate in C (es. `datetime`); si registrano solo i + loro costruttori. Incapsulare in una classe Python pura è l'unico workaround. +- **Costo al momento dell'import.** Importare `definitions` tenta sempre di importare ogni modulo in + `_MODULES`, incluse dipendenze opzionali pesanti, indipendentemente da quali moduli `-p` siano stati + richiesti. +- **Convenzione, non contratto**, tra `registry.py` ed `executor.py` (vedi sopra) — un rischio latente per i + cambiamenti futuri. +- **Il boilerplate del wrapping manuale** è il prezzo del registry guidato dalle annotazioni; non scala a + "incapsulare un'intera libreria grande" senza una certa ripetizione. + +--- + +## Roadmap / lavori rinviati + +Non fanno parte dell'attuale MVP locale; tracciati per dopo: + +- Esecuzione remota (SSH + Slurm), corrispondente alla modalità backend remota della piattaforma. +- Stadi di pipeline (coral-python come uno stadio in un DAG multi-stadio). +- Stato di esecuzione per-nodo tramite `--touch-dir` (vedi [Estendere gli interni](#punti-di-estensione-concreti)). +- Promuovere coral-python da cartella di workspace a git submodule del repository della piattaforma, una volta + containerizzato per simulare un cluster. diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md new file mode 100644 index 0000000..542088e --- /dev/null +++ b/docs/ONBOARDING.md @@ -0,0 +1,527 @@ +# coral-python — Overview + +A guide to what this project is, why it exists, how it's built, and how to extend it — for two +audiences: someone who wants to **add a CFD/scientific library** to it, and someone who wants to +**improve its internal design or its contract with the DealiiX platform**. + +This complements `README.md` (setup + day-to-day commands) and `CLAUDE.md` (mechanics reference for +AI-assisted development). This document is the *story*: goals, architecture, rationale, and an honest +account of strengths and weaknesses. + +*Last updated: 2026-07-09. Describes coral-python at the **local-MVP** stage — a coral-compatible backend +running graphs end-to-end locally; see [Roadmap / deferred work](#roadmap--deferred-work) for what is still +out of scope (remote/Slurm execution, pipeline stages).* + +## Contents + +- [Goals & context](#goals--context) +- [Architecture](#architecture) +- [Installation](#installation) +- [Use](#use) +- [Adding a new library](#adding-a-new-library-persona-a) +- [Extending internals or contracts](#extending-internals-or-contracts-persona-b) +- [Design rationale / FAQ](#design-rationale--faq) +- [Strengths & weaknesses](#strengths--weaknesses) +- [Roadmap / deferred work](#roadmap--deferred-work) + +--- + +## Goals & context + +**DealiiX platform** is a node-based visual editor: users build a graph of function calls, class +constructors, and method calls, then export it as JSON and execute it against a backend. The +original backend is **CORAL**, a C++ engine built on deal.II for finite-element simulations. + +**coral-python exists as a cross-validation proof case.** If the platform's approach — a visual +node editor talking to a backend purely through a JSON protocol — is sound, it should work against +a *second, independent* engine built with different tools for a different domain (Python + +[PhiFlow](https://github.com/tum-pbs/PhiFlow) for fluid simulation, instead of C++ + deal.II for +FEM). coral-python is that second engine. + +To make the comparison meaningful, coral-python doesn't invent its own protocol — it is +**coral-compatible**: it speaks the *same* CLI surface and the *same* JSON schema as the C++ CORAL +binary. From the platform's point of view, switching from the C++ backend to coral-python means +changing two settings (the executable path and the "plugin" value) and nothing else. See +[Architecture](#architecture) for exactly what that contract is. + +--- + +## Architecture + +### Three layers, two independent consumers + +``` + ┌─────────────────────┐ + │ definitions/ │ the single source of truth: + │ math_ops.py │ Python functions & classes, + │ string_ops.py │ with type hints + │ phiflow_defs.py │ + │ primitives.py │ + └──────────┬───────────┘ + │ + build_function_map() / build_class_map() / PRIMITIVES_MAP + │ + ┌─────────────────┴──────────────────┐ + │ │ + ▼ ▼ + ┌───────────────────┐ ┌───────────────────────┐ + │ registry.py │ │ executor.py │ + │ describes nodes │ │ runs nodes │ + │ (JSON schema) │ │ (executes a graph) │ + └─────────┬──────────┘ └───────────┬───────────┘ + │ │ + ▼ ▼ + node_types.json graph results + (→ platform sidebar) (printed / returned) +``` + +`registry.py` and `executor.py` **do not import each other.** Both import only from `definitions` +(`from definitions import PRIMITIVES_MAP, build_function_map, build_class_map` — identical line in +both files). This is deliberate: the registry's job is to *describe* what's callable; the +executor's job is to *run* it. See [Extending internals](#extending-internals-or-contracts-persona-b) +for why this decoupling matters. + +### The two contracts + +Everything the platform needs from coral-python reduces to two contracts: + +**1. The CLI contract.** `main.py` exposes the same surface as the C++ `coral` binary: + +``` +main.py -p register [--output FILE] # write the node registry +main.py -p run [--touch-dir DIR] # execute a graph +``` + +`-p`/`--plugin` is repurposed: for C++ coral it's a path to a compiled plugin (`.so`); for +coral-python it's a **comma-separated list of definition modules to load** (e.g. `"math,string"`; +empty means "load everything" — see `main.py`'s `_resolve_modules`). This is the *only* semantic +difference the platform has to know about, and it's just a string it already passes through +opaquely. The `coral-py` launcher script wraps `main.py` so the platform can point its +`coralBinaryPath` setting straight at it (see `README.md` for the exact invocation). + +**2. The JSON contract.** Two JSON shapes: + +- **Registry** (`node_types.json`, produced by `register`) — a dict keyed by each node's `type` + string, one entry per primitive/function/constructor/method. This is generated by + `registry.py:generate_registry()`. +- **Graph** (consumed by `run`) — `{"workflow": {"nodes": {...}, "edges": {...}}, ...}`, where each + node is *lean*: just `{"type": "...", "value": ...}` (primitives) or `{"type": "..."}` + (everything else). No `node_type`, no `method_name` — the executor infers what a node **is** + purely from its `type` string (see `executor.py:_classify`). This matches exactly what the + platform exports. + +### Data flow in practice + +``` +1. Probe: platform runs `coral-py -p "math,string" register` + → registry.py introspects math_ops.py + string_ops.py + → writes node_types.json + → platform reads it, populates the sidebar + +2. Build: user drags nodes onto the canvas, connects them, + platform exports a lean graph.json + +3. Run: platform runs `coral-py -p "math,string" run graph.json` + → executor.py loads graph.json, classifies each node by `type`, + topologically sorts, and calls the real Python functions/classes + → results printed to stdout (captured as the run log) +``` + +### How the registry reads signatures — `inspect.signature`, and whether it should stay + +The registry is entirely **annotation-driven**, and the mechanism that reads those annotations is the standard +library's `inspect.signature`. It's worth understanding it precisely, because it's the single fact that +explains why most libraries need a wrapper. + +**How it works.** `registry.py` calls `inspect.signature(...)` on each callable — a function +(`_add_function_node`), a constructor (`_add_constructor`, on `cls.__init__`), or a method (`_add_methods`). It +then walks `sig.parameters` (ordered, each carrying a `.annotation`) plus `sig.return_annotation`, and passes +every annotation through `python_type_to_string`, which maps it against the six-entry `PRIMITIVES_MAP` +(`int`, `float`, `str`, `bool`, `any`, `none`). Two behaviours fall out of this: + +- A **missing parameter** annotation becomes `"any"` — usable, just loosely typed. +- A **missing return** annotation produces **no output socket at all** (`_process_return_type` returns `[], []`), + so the node becomes a dead end. A `Tuple[...]` return, by contrast, becomes one output socket per element. + +`executor.py` calls `inspect.signature` independently (when binding a function, constructor, or method node) — +the two files never share a signature helper, which is the "convention, not contract" seam described under +[Extending internals](#extending-internals-or-contracts-persona-b). + +**Why it was chosen.** It's in the standard library (zero dependencies), and a single call yields ordered +parameters, defaults, and the return annotation in one uniform shape across functions, methods, and +constructors. For an annotation-driven registry it's the minimal thing that works, and it is entirely +sufficient for the code we actually own — our own typed wrappers and pure-Python annotated classes such as +`Calculator` register with no adapter at all. + +**Its honest limit.** `inspect.signature` reads only the *raw* annotations that exist on the object at runtime. +That is a hard boundary in two directions, and both are common: + +- **C-implemented code carries no runtime annotations.** Everything in `math`, most of `numpy`, and the fast + paths of scientific libraries introspect to *empty* parameters and *empty* return — so they'd register with + `"any"` inputs and no output. +- **Modern pure-Python libraries stringize their annotations.** With `from __future__ import annotations` + (PEP 563), `inspect.signature` returns the *string* `"float"` rather than the type `float`, and + `python_type_to_string`'s identity check against `PRIMITIVES_MAP` misses it → `"any"`. + +We measured how much of the real ecosystem this rules out, and the answer is sobering: across **751 public +callables** in `numpy` (461), `jax` (98), and `phi.flow` (192), **zero** are directly registrable into a clean, +wireable node — `numpy` because it's C (no annotations), `jax` because it uses PEP 563 (77 of its annotated +callables come back as strings), `phi.flow` because its types aren't primitives. Scanning every third-party +top-level module installed here, only three exposed *any* natively-usable callable, and those were incidental +helpers (`pyparsing.col`, `opt_einsum.get_symbol`, `iniconfig.iscommentline`). **The practical conclusion: +hand-written, type-hinted wrappers are the rule, not a corner case** — see +[why `math.sqrt` needs a wrapper](#why-does-mathsqrt-need-a-wrapper-cant-we-load-python-functions-dynamically). + +**The alternatives, and our opinion on each.** + +- **`typing.get_type_hints()`** — resolves PEP 563 string annotations and forward references that raw + `inspect.signature` leaves as strings. This is a cheap, low-risk change that would unlock the whole class of + modern annotated pure-Python libraries (it would, for instance, make `jax`'s stringized signatures readable). + *Our take: the one improvement worth doing first.* It doesn't fix C code (there are still no annotations to + resolve) and is still bounded by the six-primitive map, but it removes the most common avoidable failure. +- **Static AST parsing of source files** — extracts signatures without importing, dodging import side effects. + Heavier machinery, and still annotation-dependent (it reads the same hints). *Not worth it at this scale.* +- **Explicit decorator / manual schema registration** — precise and introspection-free, but it trades every + signature for hand-written boilerplate. *Only worth it if we deliberately need to register many + un-annotatable callables.* +- **`.pyi` stub reading** — the *only* route that could recover types for C functions (`numpy` et al.), since + that information lives solely in stubs. But it's high-complexity and fragile (stub discovery, version skew). + *Probably not worth it; a hand-written wrapper is simpler and more honest about intent.* + +**Bottom line.** Keep `inspect.signature` for now — it's simple and fully sufficient for the code we own. If we +later want more external libraries to "just work," the pragmatic path is `get_type_hints()` plus a richer type +map, in that order. But wrappers remain unavoidable for C and array libraries no matter which reader we choose: +that's a property of the Python ecosystem (no runtime types for compiled code), not a shortcoming of this +design. + +### How the executor runs a graph — execution order and `_classify` + +Two mechanisms in `executor.py` turn a lean graph into results. + +**Execution order.** `get_execution_order()` is a topological sort (Kahn's algorithm): it builds an adjacency +list plus an in-degree count from the edges, seeds a queue with every zero-in-degree node, and drains it, +decrementing downstream in-degrees as it goes. If the emitted order is shorter than the node count there's a +cycle, and it raises. The guarantee this buys: a node runs only after all its inputs exist, while independent +branches have no defined relative order (any valid topological order is fine). + +**Node classification.** Because lean nodes carry only `{type, value?}`, the executor has to recover what each +node *is* before it can run it. `_classify(type_str)` does exactly that, and it's deliberately cheap — a few +hash-map membership tests against maps built **once** in `__init__`, so it's effectively **O(1) per node** and +never a bottleneck: + +```python +if type_str in self.primitives_map: return "primitive" # O(1) +if type_str in self.function_map: return "function" # O(1) +if type_str in self.class_map: return "constructor" # O(1) +if "." in type_str and type_str.rsplit(".", 1)[0] in self.class_map: + return "method" # one split + O(1) +``` + +Two things worth being precise about: + +- `_classify` recovers only the node's *kind*, not its argument shape. Parameter names and order are re-derived + at call time with `inspect.signature(func | __init__ | method)` — the same reader the registry uses — and the + inputs are then bound as kwargs. That per-node `inspect.signature` call is cheap but not cached. +- The only mildly non-linear parts live elsewhere, and neither matters at today's graph sizes: the topological + sort uses a list as a queue (`queue.pop(0)` is O(n)), and each node rescans the full edge list to find its + incoming edges (`[e for e in self.edges if e["target"] == node_id]`, O(V·E) overall). Swapping in a + `collections.deque` and pre-bucketing edges by target would make a whole run linear — a clean, low-risk + [Persona B](#extending-internals-or-contracts-persona-b) win if graphs ever grow large. + +--- + +## Installation + +coral-python is a [uv](https://docs.astral.sh/uv/) project (`pyproject.toml` + `uv.lock`): + +```bash +uv sync # creates .venv, installs deps (incl. the dev group) from the lockfile +``` + +Then either activate the venv (`source .venv/bin/activate`) or prefix commands with `uv run`. See +`README.md` for the full setup section, dependency management (`uv add`), and running the test +suite. + +--- + +## Use + +```bash +# Generate the registry for one or more modules (writes node_types.json in the cwd) +uv run python main.py -p "math" register + +# Run a graph with those modules loaded +uv run python main.py -p "math" run tests/fixtures/valid_workflows/network-from-fe-math.json +``` + +Through the launcher (what the platform actually invokes): + +```bash +./coral-py -p "math,string,phiflow" register +./coral-py -p "math,string,phiflow" run graph.json +``` + +`coral-py` runs `main.py` inside this project's `.venv` via `uv run --project`, **without changing +the working directory** — so `register`'s output and the platform's configured working directory +stay consistent with what the platform expects (see the comments in `coral-py`). + +On the platform side: Settings → Execution Mode → **Local / Coral**, with the *Coral binary path* +pointed at `coral-py` and the *Coral plugin path* field holding the module list (that field accepts +free text precisely to support this — see dealiiX-platform PR #209). Then **Save & Sync** probes the +registry, and **Execute** runs a graph. + +--- + +## Adding a new library (Persona A) + +You want to add support for a CFD/scientific library other than PhiFlow — say, a different fluid +solver, a mesh library, or a numerics package. + +### The steps + +1. **Create `definitions/_ops.py`.** It must expose exactly two zero-argument functions: + + ```python + def get_functions() -> Dict[str, Any]: + return {"my_function": my_function} + + def get_classes() -> Dict[str, Any]: + return {"MyClass": MyClass} + ``` + + This is a duck-typed contract — nothing enforces it via an abstract base class, but every module + in `definitions/` follows it (see `math_ops.py`, `string_ops.py`, `phiflow_defs.py`). + +2. **Write typed wrapper functions/classes**, not raw calls into the library. See + [why wrapping is necessary](#why-does-mathsqrt-need-a-wrapper-cant-we-load-python-functions-dynamically) + below — the short version is: the registry can only produce a useful node if the function has + type-annotated parameters and a type-annotated return value. + + ```python + # definitions/mycfd_ops.py + from mycfd import Solver # the real library + + def create_solver(resolution: int) -> Any: + """Wrap Solver's constructor with an explicit, registry-friendly signature.""" + return Solver(resolution=resolution) + + def get_functions() -> Dict[str, Any]: + return {"create_solver": create_solver} + + def get_classes() -> Dict[str, Any]: + return {} + ``` + +3. **If the library might not be installed everywhere**, guard the import the way + `phiflow_defs.py` does — try the import, set an `AVAILABLE` flag, define wrapper + functions/classes only under `if AVAILABLE:`, and return `{}` from `get_functions()`/ + `get_classes()` when unavailable. This keeps coral-python importable and the other modules + working even when your library isn't installed. + +4. **Register the module** in `definitions/__init__.py` — add the import and one entry to + `_MODULES`: + + ```python + from . import math_ops, string_ops, phiflow_defs, primitives, mycfd_ops + + _MODULES = { + 'math': math_ops, + 'string': string_ops, + 'phiflow': phiflow_defs, + 'mycfd': mycfd_ops, # add here + } + ``` + + `AVAILABLE_MODULES` and both `build_function_map`/`build_class_map` pick it up automatically — + no other code changes needed. + +5. **Regenerate and check the registry**, then run a graph: + + ```bash + uv run python main.py -p "mycfd" register --output=/tmp/check.json + # inspect /tmp/check.json — every function/class you exposed should have a sensible + # arguments/inputs/outputs shape, not everything collapsed to "any" + uv run python main.py -p "mycfd" run my_test_graph.json + ``` + +### Why does `math.sqrt` need a wrapper? Can't we load Python functions dynamically? + +This comes up immediately once you look at `math_ops.py` — `math.sqrt` isn't registered directly; +instead there's a `math_sqrt(x: float) -> float` wrapper that calls it. The reason is structural, +not stylistic: + +The registry (`registry.py:generate_registry`) is **annotation-driven**. For every parameter and +return value it calls `inspect.signature(func)` and converts the annotation to a protocol type +string via `python_type_to_string`: + +```python +def python_type_to_string(py_type) -> str: + # Handle empty/missing annotations + if py_type is inspect.Signature.empty or py_type is None: + return _REVERSE_PRIMITIVES_MAP[Any] + ... +``` + +A missing annotation becomes `"any"`. Worse, for **return** values, `_process_return_type` treats a +missing annotation as *no output socket at all*: + +```python +if (return_annotation is not None + and return_annotation != type(None) + and return_annotation != inspect.Signature.empty): + return [_create_output_argument(return_annotation)], [param_idx] +return [], [] # <- missing/None annotation → zero outputs +``` + +`math.sqrt` is a C builtin (`builtin_function_or_method`). Even where `inspect.signature` succeeds +on it, the parameters and return carry **no type annotations** — that information simply doesn't +exist at runtime for C-implemented functions; it lives only in `.pyi` stub files, which nothing here +reads. Registering `math.sqrt` directly would therefore produce a node with an `"any"` input and +**no output socket** — impossible to wire into anything downstream. + +The wrapper is the smallest fix: it supplies the annotations Python's own runtime introspection +can't recover, and it's also a convenient place for logging and type coercion (e.g. converting a +NumPy scalar back to a Python `float`). This is a real, structural constraint — not a stopgap — +whenever you're wrapping a C extension or an unannotated library. + +**When you don't need a wrapper:** if the function or class is pure Python *and already carries +type hints*, register it directly — no wrapper required. That's exactly what `Calculator` in +`math_ops.py` does: its `__init__` and methods are annotated Python, so `registry.py` introspects +them without any adapter. + +--- + +## Extending internals or contracts (Persona B) + +You want to change how coral-python works internally, or evolve its contract with the platform. + +### The decoupling is real, and it's your extension point + +Because `registry.py` and `executor.py` never import each other and both consume `definitions` +only through `build_function_map`/`build_class_map`/`PRIMITIVES_MAP`, you can rewrite the entire +`definitions/` layer — a different introspection strategy, code generation, a plugin-discovery +mechanism, whatever — and both the registry generator and the executor keep working *unchanged*, +as long as: + +1. `build_function_map(include=...)` / `build_class_map(include=...)` keep returning + `{name: callable}` / `{name: class}` dicts, and +2. the JSON shape each side produces/consumes stays `{type, arguments, inputs, outputs, node_type}` + for registry entries and `{type, value?}` for lean graph nodes. + +That's a genuinely useful seam: it means "improve the registry's type system" and "improve how +nodes are discovered" are separable projects. + +**The cost of that decoupling:** it's enforced by *convention*, not by a shared interface or test +that pins both sides together. `registry.py` and `executor.py` **independently** encode the same +assumptions — e.g. that a dotted name like `"math.sqrt"` is a function, not a method (see the +comment in `executor.py:_classify`: *"functions checked before the split so dotted names like +`math.sqrt` resolve as functions, not methods"*), and that a method's `self` argument is always +input index 0. Nothing checks that a change to one side doesn't silently break the other's +assumptions — if you touch this boundary, update both and re-run the full suite (`uv run pytest`). + +### Concrete extension points + +- **Richer type system.** Only the six `PRIMITIVES_MAP` types (`int`, `float`, `str`, `bool`, `any`, + `none`) round-trip through the registry; every other annotation (a domain class, `list`, a + non-primitive tuple element) collapses to `"any"`. A richer scheme (e.g. registering domain class + names as their own protocol types, the way method `self` arguments already use the class name) + would give more precise sockets and better validation on the canvas. +- **Lazy module import.** `definitions/__init__.py` imports every module in `_MODULES` at package + import time — including `phiflow_defs`, which attempts the full PhiFlow/JAX import chain + regardless of whether `-p` selected it. Importing only the modules named in `include` would avoid + paying for unused dependencies. +- **Per-node execution status.** The CLI accepts `--touch-dir` for compatibility with the platform's + live per-node status feature, but doesn't yet write anything there — `executor.py` would need to + emit a status file per node as it executes. +- **Enforcing the registry/executor convention.** A shared test (or a single source of "how to + classify a `type` string") that both `registry.py` and `executor.py` are checked against would + remove the "convention, not enforcement" risk described above. +- **Linear-time execution.** The executor's topological sort uses a list as a queue and each node rescans the + full edge list for its inputs (see [How the executor runs a graph](#how-the-executor-runs-a-graph--execution-order-and-_classify)). + A `collections.deque` plus edges pre-bucketed by target makes a whole run linear — not needed at today's + sizes, but a clean win before scaling to large graphs. (Note: `_classify` itself is already O(1) per node.) + +--- + +## Design rationale / FAQ + +### Why does `math.sqrt` need a wrapper? Can't we load Python functions dynamically with no manual wrapping? + +Answered in full [above](#why-does-mathsqrt-need-a-wrapper-cant-we-load-python-functions-dynamically). +Short version: the registry is annotation-driven, and Python doesn't expose runtime type +annotations for C-implemented functions — there's nothing to introspect. Pure Python functions and +classes *with* type hints (like `Calculator`) need no wrapper at all. + +### Does the registry/executor decoupling really let someone rewrite `definitions` under the same contract? + +Yes — see [Extending internals](#the-decoupling-is-real-and-its-your-extension-point) above. It's a +genuine architectural property (verified: neither module imports the other; both only touch +`definitions`'s public surface), with one honest caveat: the split is convention-based, not +contract-enforced, so changes on one side need a matching check on the other. + +### Why are `_MODULES` and the `build_*_map` functions defined in `definitions/__init__.py` instead of somewhere else? + +This is a standard Python idiom: a package's `__init__.py` acting as a small **plugin registry** — +it aggregates sibling modules (`math_ops`, `string_ops`, `phiflow_defs`, ...) that each satisfy a +duck-typed contract (`get_functions()`/`get_classes()`), and exposes a couple of factory functions +(`build_function_map`, `build_class_map`) as the package's public API. This is common and +appropriate at this scale — you'd reach for something heavier (setuptools entry points, a decorator- +based registration system) only if modules needed to be discoverable from *outside* this package +(e.g. as installable third-party plugins), which isn't the case here. + +Two things worth knowing if you work in this file: +- `FUNCTION_MAP`/`CLASS_MAP` are also built **eagerly at import time**, "for backward compatibility" + per the comment — but neither `registry.py` nor `executor.py` actually uses them; both call + `build_function_map(include=...)`/`build_class_map(include=...)` with an explicit module list. + Those two globals are vestigial. +- The two `build_*_map` functions duplicate the same include/exclude resolution logic. Also, + because both call `.update()` into a shared dict, if two modules define the same key (today, + `print_result` exists in both `math_ops.py` and `string_ops.py`) the later module silently wins. + Harmless today since the duplicate is identical, but worth knowing before adding a colliding name. + +--- + +## Strengths & weaknesses + +**Strengths** + +- Clean three-layer separation (`definitions` → `registry`/`executor`) with a real, verifiable + decoupling between describing and running a graph. +- Genuinely coral-compatible: same CLI surface, same JSON schema as the C++ backend — the platform + needs zero backend-specific code to drive it. +- The lean, type-keyed graph protocol matches the platform's current export format exactly (no + adapter needed on the platform side). +- Graceful optional-dependency handling (`phiflow_defs.py`'s `AVAILABLE` guard) — the package stays + importable and other modules still work if PhiFlow isn't installed. +- Small, well-tested surface: 88 passing tests covering registry generation, execution, and module + loading. + +**Weaknesses** + +- **Lossy type system** — only six primitive types round-trip through the registry; everything + else becomes `"any"`, which weakens connection validation on the canvas. +- **Annotation asymmetry** — a missing parameter annotation becomes `"any"` (still usable), but a + missing return annotation produces *no output socket* (the node becomes a dead end). Easy to trip + over when writing a new wrapper. +- **C-extension methods are silently dropped.** `_add_methods`'s `inspect.isfunction` check filters + out methods of C-implemented classes (e.g. `datetime`); only their constructors register. Wrapping + in a pure-Python class is the only workaround. +- **Import-time cost.** Importing `definitions` always attempts to import every module in + `_MODULES`, including heavy optional dependencies, regardless of which `-p` modules were + requested. +- **Convention, not contract**, between `registry.py` and `executor.py` (see above) — a latent risk + for future changes. +- **Manual-wrapping boilerplate** is the price of the annotation-driven registry; it doesn't scale + to "wrap an entire large library" without some repetition. + +--- + +## Roadmap / deferred work + +Not part of the current local MVP; tracked for later: + +- Remote execution (SSH + Slurm), matching the platform's remote backend mode. +- Pipeline stages (coral-python as one stage in a multi-stage DAG). +- Per-node execution status via `--touch-dir` (see [Extending internals](#concrete-extension-points)). +- Promoting coral-python from a workspace folder to a git submodule of the platform repo, once it's + containerized to simulate a cluster. diff --git a/executor.py b/executor.py index 86b30e8..41f8a71 100644 --- a/executor.py +++ b/executor.py @@ -69,6 +69,30 @@ def get_execution_order(self) -> List[str]: return order + def _classify(self, type_str: str) -> str: + """Infer a node's kind from its ``type`` using the loaded definition maps. + + The graph protocol identifies every node by ``type`` (primitives by type name, functions by + name, constructors by class name, methods by ``Class.method``); the kind is derived here + rather than read from the node, so lean graphs that omit ``node_type`` behave the same as + full ones. Functions are checked before the method split so dotted names like ``math.sqrt`` + resolve as functions, not methods. + + Raises: + ValueError: if ``type_str`` matches no loaded primitive, function, class, or method. + """ + if type_str in self.primitives_map: + return "primitive" + if type_str in self.function_map: + return "function" + if type_str in self.class_map: + return "constructor" + if "." in type_str and type_str.rsplit(".", 1)[0] in self.class_map: + return "method" + raise ValueError( + f"Unknown node type '{type_str}': not a loaded primitive, function, class, or method" + ) + def execute(self): """Execute the workflow""" order = self.get_execution_order() @@ -76,7 +100,7 @@ def execute(self): for node_id in order: node = self.nodes[node_id] - node_type = node.get("node_type", "function") + node_type = self._classify(node["type"]) if node_type == "primitive": # Elementary nodes just return their value with type conversion @@ -100,7 +124,7 @@ def execute(self): elif node_type == "function": # function nodes execute a function with inputs source edges - func_name = node["method_name"] + func_name = node["type"] func = self.function_map[func_name] # Get incoming edges for this node @@ -197,7 +221,7 @@ def execute(self): elif node_type == "method": # Method nodes call an instance method # Parse fully qualified name: "ClassName.method_name" - fully_qualified_name = node["method_name"] + fully_qualified_name = node["type"] class_name, method_name = fully_qualified_name.rsplit(".", 1) # Get incoming edges diff --git a/main.py b/main.py index 6ddc5f1..466d273 100644 --- a/main.py +++ b/main.py @@ -1,48 +1,99 @@ +"""Coral-compatible CLI for the Python backend. + +Presents the same command surface as the C++ ``coral`` binary so the DealiiX platform can drive this +backend by changing only the executable and plugin paths: + + main.py -p register # write the node registry (node_types.json) into the cwd + main.py -p run # execute a workflow graph + +For this Python backend ``-p/--plugin`` names the definition modules to load (comma-separated, e.g. +``"math,string"``); an empty value loads every available module. ``-p/--plugin`` must appear before the +subcommand. See the integration plan in issue #12. +""" + import argparse +from definitions import AVAILABLE_MODULES from registry import save_registry_to_file from executor import WorkflowExecutor +# Fixed filename the DealiiX platform probes for after `register`. +DEFAULT_REGISTRY_FILENAME = "node_types.json" +# Workflow used when `run` is given no graph argument (keeps the pre-refactor default reachable). +DEFAULT_WORKFLOW_FILE = "network-from-fe.json" + def main(): - """Main entry point for the Coral workflow system""" - parser = argparse.ArgumentParser(description='Execute a workflow from a JSON file') - parser.add_argument('workflow_file', - nargs='?', # Makes it optional - default='network-from-fe.json', - help='Path to the workflow JSON file (default: network-from-fe.json)' + """Parse coral-style CLI arguments and dispatch to the ``register`` or ``run`` subcommand. + + The top-level parser owns the global ``-p/--plugin`` option (mirroring coral's plugin flag); + ``register`` and ``run`` are subcommands. ``register`` writes the node registry to a JSON file + in the current working directory; ``run`` executes a workflow graph via ``WorkflowExecutor``. + """ + parser = argparse.ArgumentParser( + prog="main.py", + description="Coral-compatible CLI: generate the node registry or run a workflow graph.", ) + # Global option mirroring coral's plugin flag. For the Python backend it names the definition + # modules to load (comma-separated); an empty value means "load all available modules". + parser.add_argument( + "-p", "--plugin", + default="", + metavar="MODULES", + help="Comma-separated definition modules to load (e.g. 'math,string'); empty loads all. " + "Must precede the subcommand.", + ) + + subparsers = parser.add_subparsers(dest="command", required=True) - # Add option to generate registry - parser.add_argument('--generate-registry', - action='store_true', - help='Generate the registry file from definitions.py' + register_parser = subparsers.add_parser( + "register", + help="Generate the node registry and write it to a JSON file in the current directory.", ) - parser.add_argument('--registry-output', - default='registry-py.json', - help='Output path for the registry file (default: registry-py.json)' + register_parser.add_argument( + "--output", + default=DEFAULT_REGISTRY_FILENAME, + help=f"Registry output filename, relative to the cwd (default: {DEFAULT_REGISTRY_FILENAME}).", ) - # Add option to specify which modules to load - parser.add_argument('--modules', - default='phiflow', - help='Comma-separated list of modules to load (options: math, string, phiflow). Default: phiflow' + run_parser = subparsers.add_parser("run", help="Execute a workflow graph from a JSON file.") + run_parser.add_argument( + "graph", + nargs="?", + default=DEFAULT_WORKFLOW_FILE, + help=f"Path to the workflow JSON graph (default: {DEFAULT_WORKFLOW_FILE}).", + ) + run_parser.add_argument( + "--touch-dir", + default=None, + help="Directory for per-node status files. Accepted for coral compatibility; not yet emitted.", ) args = parser.parse_args() + modules = _resolve_modules(args.plugin) - # Parse module list - module_list = [m.strip() for m in args.modules.split(',') if m.strip()] - - # Generate registry if requested - if args.generate_registry: - save_registry_to_file(args.registry_output, modules=module_list) - else: - # Normal workflow execution - executor = WorkflowExecutor(args.workflow_file, modules=module_list) + if args.command == "register": + save_registry_to_file(args.output, modules=modules) + elif args.command == "run": + executor = WorkflowExecutor(args.graph, modules=modules) results = executor.execute() print(f"\nFinal results: {results}") +# ── Private helpers ── + + +def _resolve_modules(plugin_value): + """Resolve the ``-p/--plugin`` value into an explicit list of module names. + + Splits a comma-separated value into module names, ignoring blank entries. An empty or + whitespace-only value resolves to all available modules — this is passed explicitly (rather + than relying on ``None``) because ``save_registry_to_file``/``WorkflowExecutor`` default a + ``None`` module list to ``['phiflow']`` only. + """ + modules = [m.strip() for m in plugin_value.split(",") if m.strip()] + return modules if modules else list(AVAILABLE_MODULES) + + if __name__ == "__main__": main() diff --git a/network-from-fe.json b/network-from-fe.json index 608de51..593b0f5 100644 --- a/network-from-fe.json +++ b/network-from-fe.json @@ -2,660 +2,221 @@ "workflow": { "nodes": { "7": { - "value": "100", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "100", "position": { "x": 70.03667203790297, "y": 137.90803590670052 } }, "13": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "50", "position": { "x": 124.55833747868672, "y": 312.7979999970585 } }, "14": { - "value": "5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "5", "position": { "x": 98.13827531375492, "y": 557.470749611427 } }, "15": { - "value": "9.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "9.5", "position": { "x": 115.36875063871042, "y": 441.4522157567264 } }, "17": { - "value": "200", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "200", "position": { "x": 802.2432890176724, "y": 516.2100729129198 } }, "19": { - "value": "64", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "64", "position": { "x": 804.4516595293679, "y": 302.44420743338173 } }, "21": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "50", "position": { "x": 1526.8776428253366, "y": 689.7215054177414 } }, "22": { - "value": "0.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "0.5", "position": { "x": 1480.5794687992932, "y": 822.1774347342322 } }, "23": { - "value": "3", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "3", "position": { "x": 1477.096307426024, "y": 935.9834225263783 } }, "26": { - "value": "simulation.mp4", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "str", - "is_valid": true, + "value": "simulation.mp4", "position": { "x": 2118.0889514499736, "y": 787.0272760951017 } }, "27": { - "value": "15", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "15", "position": { "x": 2078.183843839894, "y": 995.4817404467145 } }, "28": { - "value": "150", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "150", "position": { "x": 2078.150296083115, "y": 1101.6659513023124 } }, "29": { - "value": "80", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "80", "position": { "x": 2126.8559733597726, "y": 891.2574254671499 } }, "31": { - "value": "10", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "10", "position": { "x": 1342.6514565166847, "y": 123.01146445601489 } }, "32": { - "value": "15", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "15", "position": { "x": 1337.965084312094, "y": 25.130396403032414 } }, "33": { - "value": "60", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "60", "position": { "x": 1338.5681890028923, "y": -83.42278897360073 } }, "34": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "50", "position": { "x": 1341.5820301371482, "y": -207.61745440570436 } }, "36": { - "value": null, - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "none", - "is_valid": true, + "value": null, "position": { "x": 1819.185507960884, "y": -216.6183987416331 } }, "38": { - "arguments": [ - { - "connection_type": "input", - "type": "float", - "name": "x" - }, - { - "connection_type": "input", - "type": "float", - "name": "y" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowBox", - "is_valid": true, "position": { "x": 560.050773885825, "y": 117.84083829398213 } }, "39": { - "arguments": [ - { - "connection_type": "input", - "type": "float", - "name": "x" - }, - { - "connection_type": "input", - "type": "float", - "name": "y" - }, - { - "connection_type": "input", - "type": "float", - "name": "radius" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowSphere", - "is_valid": true, "position": { "x": 583.6332239434627, "y": 407.6370953076763 } }, "40": { - "arguments": [ - { - "connection_type": "input", - "type": "any", - "name": "domain_box" - }, - { - "connection_type": "input", - "type": "int", - "name": "resolution_x" - }, - { - "connection_type": "input", - "type": "int", - "name": "resolution_y" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowStaggeredGrid", - "is_valid": true, "position": { "x": 1298.9393072915784, "y": 249.15614843810573 } }, "41": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowBox", - "name": "self" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box", - "is_valid": true, "position": { "x": 913.5085188820058, "y": 119.04180249358025 } }, "42": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowSphere", - "name": "self" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere", - "is_valid": true, "position": { "x": 917.4607143930223, "y": 690.018268597996 } }, "43": { - "arguments": [ - { - "connection_type": "input", - "type": "any", - "name": "domain_box" - }, - { - "connection_type": "input", - "type": "int", - "name": "resolution_x" - }, - { - "connection_type": "input", - "type": "int", - "name": "resolution_y" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCenteredGrid", - "is_valid": true, "position": { "x": 1303.5323833025097, "y": 511.0199042738674 } }, "45": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowStaggeredGrid", - "name": "self" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid", - "is_valid": true, "position": { "x": 1758.8348621333253, "y": 306.3188240955864 } }, "49": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowCenteredGrid", - "name": "self" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid", - "is_valid": true, "position": { "x": 1722.8076458521364, "y": 472.2196909222935 } }, "50": { - "arguments": [ - { - "connection_type": "input", - "type": "float", - "name": "center_x" - }, - { - "connection_type": "input", - "type": "float", - "name": "center_y" - }, - { - "connection_type": "input", - "type": "float", - "name": "half_size_x" - }, - { - "connection_type": "input", - "type": "float", - "name": "half_size_y" - } - ], - "inputs": [ - 0, - 1, - 2, - 3 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCuboid", - "is_valid": true, "position": { "x": 1942.7310154398988, "y": -77.85734938335152 } }, "51": { - "arguments": [ - { - "connection_type": "input", - "type": "any", - "name": "velocity_grid" - }, - { - "connection_type": "input", - "type": "any", - "name": "smoke_grid" - }, - { - "connection_type": "input", - "type": "int", - "name": "time_steps" - }, - { - "connection_type": "input", - "type": "float", - "name": "dt" - }, - { - "connection_type": "input", - "type": "int", - "name": "substeps" - }, - { - "connection_type": "input", - "type": "any", - "name": "obstacles" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6, - 7, - 8 - ], - "node_type": "function", - "method_name": "phiflow_iterate", - "is_valid": true, + "type": "phiflow_iterate", "position": { "x": 2255.309302939301, "y": 339.4883710973994 } }, "52": { - "arguments": [ - { - "connection_type": "input", - "type": "any", - "name": "smoke_trajectory" - }, - { - "connection_type": "input", - "type": "str", - "name": "output_filename" - }, - { - "connection_type": "input", - "type": "int", - "name": "frame_time" - }, - { - "connection_type": "input", - "type": "int", - "name": "fps" - }, - { - "connection_type": "input", - "type": "int", - "name": "dpi" - }, - { - "connection_type": "input", - "type": "any", - "name": "obstacles" - }, - { - "connection_type": "output", - "type": "any", - "name": "" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6 - ], - "node_type": "function", - "method_name": "phiflow_plot_and_save", - "is_valid": true, + "type": "phiflow_plot_and_save", "position": { "x": 2709.5801106336166, "y": 289.0822994647512 @@ -854,4 +415,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-31T10:03:13.127Z" -} \ No newline at end of file +} diff --git a/phi_flow/README.md b/phi_flow/README.md index 92dc49e..585a2d9 100644 --- a/phi_flow/README.md +++ b/phi_flow/README.md @@ -14,7 +14,9 @@ python phi_flow/examples/smoke_plume/smoke_plume.py python phi_flow/examples/parallel_simulations/parallel-simulation-script.py ``` -Output files (`.gif`, `.mp4`) are generated in the script's working directory. +Output files (`.gif`, `.mp4`) are generated in the script's working directory. `.mp4` export calls +matplotlib's `anim.save(..., writer='ffmpeg')`, which requires the `ffmpeg` binary installed and on +`PATH` (it's not a pip package). `.gif` export works without it. ## Physics Background diff --git a/phi_flow/one_obstacle.gif b/phi_flow/one_obstacle.gif index 6743198..a18ddf6 100644 Binary files a/phi_flow/one_obstacle.gif and b/phi_flow/one_obstacle.gif differ diff --git a/phi_flow/one_obstacle.mp4 b/phi_flow/one_obstacle.mp4 index 6ac9256..1e7c283 100644 Binary files a/phi_flow/one_obstacle.mp4 and b/phi_flow/one_obstacle.mp4 differ diff --git a/phi_flow/one_obstacle.py b/phi_flow/one_obstacle.py new file mode 100644 index 0000000..d3b4a49 --- /dev/null +++ b/phi_flow/one_obstacle.py @@ -0,0 +1,43 @@ +# %% +# %pip install phiflow +# from phi.jax.flow import * +# from phi.torch.flow import * +from phi.flow import * # If JAX is not installed. You can use phi.torch or phi.tf as well. + +# %% +domain = Box(x=100, y=100) + +inflow_rate = 0.2 +inflow_x = 55 +obstacle_x = 50 + +# print(obstacle_x) + +# %% +obstacle = Cuboid(vec(x=obstacle_x, y=60), half_size=vec(x=15, y=10)) + +inflow = Sphere(x=inflow_x, y=9.5, radius=5) +plot(obstacle, inflow, overlay='args') + +# %% +@jit_compile +def step(v, s, p, dt=1.): + s = advect.mac_cormack(s, v, dt) + inflow_rate * resample(inflow, to=s, soft=True) + buoyancy = resample(s * (0, 0.1), to=v) + v = advect.semi_lagrangian(v, v, dt) + buoyancy * dt + v, p = fluid.make_incompressible(v, obstacle, Solve('scipy-direct', 1e-1, x0=p)) + return v, s, p + +v0 = StaggeredGrid(0, 0, domain, x=64, y=64) +smoke0 = CenteredGrid(0, ZERO_GRADIENT, domain, x=200, y=200) + +v_trj, s_trj, p_trj = iterate(step, batch(time=100), v0, smoke0, None, dt=.5, substeps=3) + +# %% +anim = plot(obstacle, inflow, s_trj, animate='time', overlay='args') +anim.save('one_obstacle.gif') +anim.save('one_obstacle.mp4', writer='ffmpeg', fps=15, dpi=150) + + + +# %% diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..aa543fc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "coral-python" +version = "0.1.0" +description = "Python cross-validation backend for the DealiiX platform: executes JSON node-graph workflows and emits a node registry." +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "phiflow", + "jax", + "h5py", +] + +[dependency-groups] +dev = [ + "pytest", + "pytest-cov", +] + +# Flat-script layout (main.py / executor.py / registry.py at the repo root, imported by +# module name rather than as an installed package). Treat this as a non-packaged uv project: +# `uv sync` installs the dependencies into .venv but does not try to build coral-python itself. +[tool.uv] +package = false diff --git a/pytest.ini b/pytest.ini index a665f28..306255b 100644 --- a/pytest.ini +++ b/pytest.ini @@ -35,4 +35,5 @@ filterwarnings = ignore::DeprecationWarning ignore::PendingDeprecationWarning ignore:jit_compile\(\) not supported by numpy:RuntimeWarning - ignore:spsolve requires A be CSC or CSR matrix format:scipy.sparse._base.SparseEfficiencyWarning \ No newline at end of file + ignore:spsolve requires A be CSC or CSR matrix format:scipy.sparse._base.SparseEfficiencyWarning + ignore:Rank deficiency >= 1 detected in linear solve:RuntimeWarning \ No newline at end of file diff --git a/registry-py.json b/registry-py.json index 07bb306..dfdf8db 100644 --- a/registry-py.json +++ b/registry-py.json @@ -1,6 +1,7 @@ { - "0": { - "value": null, + "int": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -8,8 +9,9 @@ "node_type": "primitive", "type": "int" }, - "1": { - "value": null, + "float": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -17,8 +19,9 @@ "node_type": "primitive", "type": "float" }, - "2": { - "value": null, + "str": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -26,8 +29,9 @@ "node_type": "primitive", "type": "str" }, - "3": { - "value": null, + "bool": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -35,8 +39,9 @@ "node_type": "primitive", "type": "bool" }, - "4": { - "value": null, + "any": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -44,8 +49,9 @@ "node_type": "primitive", "type": "any" }, - "5": { - "value": null, + "none": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -53,7 +59,212 @@ "node_type": "primitive", "type": "none" }, - "6": { + "print_result": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "value" + } + ], + "inputs": [ + 0 + ], + "outputs": [], + "node_type": "function", + "type": "print_result" + }, + "add": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "a" + }, + { + "connection_type": "input", + "type": "float", + "name": "b" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "function", + "type": "add" + }, + "multiply": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "a" + }, + { + "connection_type": "input", + "type": "float", + "name": "b" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "function", + "type": "multiply" + }, + "math.sqrt": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "function", + "type": "math.sqrt" + }, + "math.sin": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "function", + "type": "math.sin" + }, + "math.cos": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "function", + "type": "math.cos" + }, + "math.pow": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "input", + "type": "float", + "name": "y" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "function", + "type": "math.pow" + }, + "test_tuple_return": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "input", + "type": "float", + "name": "y" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2, + 3, + 4 + ], + "node_type": "function", + "type": "test_tuple_return" + }, + "phiflow_iterate": { "arguments": [ { "connection_type": "input", @@ -115,9 +326,9 @@ 8 ], "node_type": "function", - "method_name": "phiflow_iterate" + "type": "phiflow_iterate" }, - "7": { + "phiflow_plot_and_save": { "arguments": [ { "connection_type": "input", @@ -167,9 +378,9 @@ 6 ], "node_type": "function", - "method_name": "phiflow_plot_and_save" + "type": "phiflow_plot_and_save" }, - "8": { + "phiflow_union": { "arguments": [ { "connection_type": "input", @@ -219,9 +430,43 @@ 6 ], "node_type": "function", - "method_name": "phiflow_union" + "type": "phiflow_union" + }, + "Calculator": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "initial_value" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + -1 + ], + "node_type": "constructor", + "type": "Calculator" + }, + "StringProcessor": { + "arguments": [ + { + "connection_type": "input", + "type": "str", + "name": "prefix" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + -1 + ], + "node_type": "constructor", + "type": "StringProcessor" }, - "9": { + "PhiFlowBox": { "arguments": [ { "connection_type": "input", @@ -244,7 +489,7 @@ "node_type": "constructor", "type": "PhiFlowBox" }, - "10": { + "PhiFlowSphere": { "arguments": [ { "connection_type": "input", @@ -273,7 +518,7 @@ "node_type": "constructor", "type": "PhiFlowSphere" }, - "11": { + "PhiFlowStaggeredGrid": { "arguments": [ { "connection_type": "input", @@ -302,7 +547,7 @@ "node_type": "constructor", "type": "PhiFlowStaggeredGrid" }, - "12": { + "PhiFlowCenteredGrid": { "arguments": [ { "connection_type": "input", @@ -331,7 +576,7 @@ "node_type": "constructor", "type": "PhiFlowCenteredGrid" }, - "13": { + "PhiFlowCuboid": { "arguments": [ { "connection_type": "input", @@ -366,7 +611,147 @@ "node_type": "constructor", "type": "PhiFlowCuboid" }, - "14": { + "Calculator.add_to_value": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "float", + "name": "amount" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "method", + "type": "Calculator.add_to_value" + }, + "Calculator.get_value": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "method", + "type": "Calculator.get_value" + }, + "Calculator.multiply_value": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "float", + "name": "factor" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "method", + "type": "Calculator.multiply_value" + }, + "StringProcessor.concatenate": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "str", + "name": "text" + }, + { + "connection_type": "output", + "type": "str", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "method", + "type": "StringProcessor.concatenate" + }, + "StringProcessor.repeat": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "str", + "name": "text" + }, + { + "connection_type": "input", + "type": "int", + "name": "times" + }, + { + "connection_type": "output", + "type": "str", + "name": "" + } + ], + "inputs": [ + 0, + 1, + 2 + ], + "outputs": [ + 3 + ], + "node_type": "method", + "type": "StringProcessor.repeat" + }, + "PhiFlowBox.get_box": { "arguments": [ { "connection_type": "input", @@ -386,10 +771,9 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box" }, - "15": { + "PhiFlowSphere.get_sphere": { "arguments": [ { "connection_type": "input", @@ -409,10 +793,9 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere" }, - "16": { + "PhiFlowStaggeredGrid.get_grid": { "arguments": [ { "connection_type": "input", @@ -432,10 +815,9 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid" }, - "17": { + "PhiFlowCenteredGrid.get_grid": { "arguments": [ { "connection_type": "input", @@ -455,10 +837,9 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid" }, - "18": { + "PhiFlowCuboid.get_cuboid": { "arguments": [ { "connection_type": "input", @@ -478,7 +859,6 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowCuboid.get_cuboid", "type": "PhiFlowCuboid.get_cuboid" } } \ No newline at end of file diff --git a/registry.py b/registry.py index 826134b..312c383 100644 --- a/registry.py +++ b/registry.py @@ -51,8 +51,8 @@ def _process_return_type(return_annotation, param_idx: int): return [], [] -def _add_function_node(registry: Dict, node_id: int, func_name: str, func: callable) -> int: - """Add a function node to the registry and return next node_id""" +def _add_function_node(registry: Dict, func_name: str, func: callable) -> None: + """Add a function node to the registry, keyed by its name.""" sig = inspect.signature(func) # Process input parameters @@ -67,18 +67,19 @@ def _add_function_node(registry: Dict, node_id: int, func_name: str, func: calla output_arguments, outputs = _process_return_type(sig.return_annotation, param_idx) arguments.extend(output_arguments) - registry[str(node_id)] = { + # `type` is the function name — the single node identifier (the editor looks entries up as + # registry[type], and graphs reference nodes by type). + registry[func_name] = { "arguments": arguments, "inputs": inputs, "outputs": outputs, "node_type": "function", - "method_name": func_name, + "type": func_name, } - return node_id + 1 -def _add_constructor(registry: Dict, node_id: int, class_name: str, cls: type) -> int: - """Add a constructor node to the registry and return next node_id""" +def _add_constructor(registry: Dict, class_name: str, cls: type) -> None: + """Add a constructor node to the registry, keyed by the class name.""" init_sig = inspect.signature(cls.__init__) # Process constructor parameters (skip 'self') @@ -93,18 +94,17 @@ def _add_constructor(registry: Dict, node_id: int, class_name: str, cls: type) - inputs.append(param_idx) param_idx += 1 - registry[str(node_id)] = { + registry[class_name] = { "arguments": arguments, "inputs": inputs, "outputs": [-1], "node_type": "constructor", - "type": class_name + "type": class_name, } - return node_id + 1 -def _add_methods(registry: Dict, node_id: int, class_name: str, cls: type) -> int: - """Add all public methods of a class to the registry and return next node_id""" +def _add_methods(registry: Dict, class_name: str, cls: type) -> None: + """Add all public methods of a class to the registry, keyed by 'Class.method'.""" for method_name in dir(cls): # Skip private and dunder methods if method_name.startswith('_'): @@ -136,52 +136,65 @@ def _add_methods(registry: Dict, node_id: int, class_name: str, cls: type) -> in fully_qualified_name = f"{class_name}.{method_name}" - registry[str(node_id)] = { + registry[fully_qualified_name] = { "arguments": arguments, "inputs": inputs, "outputs": outputs, "node_type": "method", - "method_name": fully_qualified_name, - "type": fully_qualified_name + "type": fully_qualified_name, } - node_id += 1 - - return node_id def generate_registry( function_map: Dict[str, callable], primitives: List[str] = None, class_map: Dict[str, type] = None ) -> Dict: - """Generate a registry JSON from definitions""" + """Generate the node registry in the DealiiX platform format. + + Introspects the given function/class maps and primitive type names and returns a dict keyed by + each node's ``type`` string (primitives by type name, functions by name, constructors by class + name, methods by ``Class.method``). + + Args: + function_map: Mapping of function name -> callable. + primitives: List of primitive type names to include (always added). + class_map: Optional mapping of class name -> class (adds constructors and methods). + + Returns: + The registry dict keyed by node ``type``. + + Raises: + ValueError: if ``primitives`` or ``function_map`` is None. + """ if primitives is None or function_map is None: raise ValueError("primitives and function_map must be provided") registry = {} - node_id = 0 - # Add primitive types + # Add primitive types, keyed by the primitive type name. Primitives take no inputs, but the + # empty `arguments` list is required: the platform's registry validator skips any entry lacking + # an `arguments` key. for prim_type in primitives: - registry[str(node_id)] = { - "value": None, + registry[prim_type] = { + "arguments": [], + "value": "", "inputs": [], "outputs": [-1], "node_type": "primitive", "type": prim_type, } - node_id += 1 # Add functions for func_name, func in function_map.items(): - node_id = _add_function_node(registry, node_id, func_name, func) + _add_function_node(registry, func_name, func) # Add class constructors and methods if class_map: for class_name, cls in class_map.items(): - node_id = _add_constructor(registry, node_id, class_name, cls) + _add_constructor(registry, class_name, cls) for class_name, cls in class_map.items(): - node_id = _add_methods(registry, node_id, class_name, cls) + _add_methods(registry, class_name, cls) return registry diff --git a/tests/README.md b/tests/README.md index 3750fea..3c616dc 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,8 +13,8 @@ tests/ ├── test_modules.py # Module loading tests ├── test_integration.py # End-to-end workflow tests └── fixtures/ - ├── valid_workflows/ # Valid workflow test files - └── invalid_workflows/ # Invalid workflows for error testing + ├── valid_workflows/ # Valid workflow test files (lean: nodes keyed by id, identified by type) + └── valid_nodes/ # Registry fixtures (node-type definitions) ``` ## Running Tests diff --git a/tests/conftest.py b/tests/conftest.py index 19e9cc4..958fdef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -75,18 +75,15 @@ def simple_workflow_dict() -> Dict[str, Any]: "workflow": { "nodes": { "node1": { - "node_type": "primitive", "type": "int", "value": 5 }, "node2": { - "node_type": "primitive", "type": "int", "value": 3 }, "node3": { - "node_type": "function", - "method_name": "add" + "type": "add" } }, "edges": { @@ -114,12 +111,10 @@ def circular_workflow_dict() -> Dict[str, Any]: "workflow": { "nodes": { "node1": { - "node_type": "function", - "method_name": "add" + "type": "add" }, "node2": { - "node_type": "function", - "method_name": "multiply" + "type": "multiply" } }, "edges": { diff --git a/tests/fixtures/invalid_workflows/circular_dependency.json b/tests/fixtures/invalid_workflows/circular_dependency.json deleted file mode 100644 index 55a11d2..0000000 --- a/tests/fixtures/invalid_workflows/circular_dependency.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "workflow": { - "nodes": [ - { - "id": "node1", - "node_type": "function", - "method_name": "add" - }, - { - "id": "node2", - "node_type": "function", - "method_name": "multiply" - } - ], - "edges": [ - { - "source": "node1", - "target": "node2", - "source_output": 0, - "target_input": 0 - }, - { - "source": "node2", - "target": "node1", - "source_output": 0, - "target_input": 0 - } - ] - } -} diff --git a/tests/fixtures/invalid_workflows/invalid_function.json b/tests/fixtures/invalid_workflows/invalid_function.json deleted file mode 100644 index 1e52262..0000000 --- a/tests/fixtures/invalid_workflows/invalid_function.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "workflow": { - "nodes": [ - { - "id": "node1", - "node_type": "primitive", - "type": "int", - "value": 5 - }, - { - "id": "node2", - "node_type": "function", - "method_name": "nonexistent_function" - } - ], - "edges": [ - { - "source": "node1", - "target": "node2", - "source_output": 0, - "target_input": 0 - } - ] - } -} \ No newline at end of file diff --git a/tests/fixtures/invalid_workflows/missing_node.json b/tests/fixtures/invalid_workflows/missing_node.json deleted file mode 100644 index aae34c6..0000000 --- a/tests/fixtures/invalid_workflows/missing_node.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "workflow": { - "nodes": [ - { - "id": "node1", - "node_type": "primitive", - "type": "int", - "value": 5 - } - ], - "edges": [ - { - "source": "node1", - "target": "nonexistent_node", - "source_output": 0, - "target_input": 0 - } - ] - } -} \ No newline at end of file diff --git a/tests/fixtures/valid_nodes/README.md b/tests/fixtures/valid_nodes/README.md index 1c0a556..0355a91 100644 --- a/tests/fixtures/valid_nodes/README.md +++ b/tests/fixtures/valid_nodes/README.md @@ -4,14 +4,18 @@ This directory contains registry JSON files used for testing the node type syste ## Registry Files -- `registry-math.json` - Registry for math module (mathematical operations and Calculator class) -- `registry-phiflow.json` - Registry for PhiFlow physics simulation module -- `registry-py.json` - Default/combined registry file +- `registry-math.json` - Registry for the math module (mathematical operations and Calculator class) +- `registry-phiflow.json` - Registry for the PhiFlow physics simulation module +- `registry-py.json` - Combined registry for all modules (math, string, phiflow) + +All files use the DealiiX platform registry format: entries are keyed by node `type` and each is +marked `is_valid: true`. These files are auto-generated using: ```bash -python main.py --generate-registry --modules="math" --registry-output="tests/fixtures/valid_nodes/registry-math.json" -python main.py --generate-registry --modules="phiflow" --registry-output="tests/fixtures/valid_nodes/registry-phiflow.json" +python main.py -p "math" register --output="tests/fixtures/valid_nodes/registry-math.json" +python main.py -p "phiflow" register --output="tests/fixtures/valid_nodes/registry-phiflow.json" +python main.py register --output="tests/fixtures/valid_nodes/registry-py.json" ``` ## Usage in Tests diff --git a/tests/fixtures/valid_nodes/registry-math.json b/tests/fixtures/valid_nodes/registry-math.json index 26e1633..c331d65 100644 --- a/tests/fixtures/valid_nodes/registry-math.json +++ b/tests/fixtures/valid_nodes/registry-math.json @@ -1,6 +1,7 @@ { - "0": { - "value": null, + "int": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -8,8 +9,9 @@ "node_type": "primitive", "type": "int" }, - "1": { - "value": null, + "float": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -17,8 +19,9 @@ "node_type": "primitive", "type": "float" }, - "2": { - "value": null, + "str": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -26,8 +29,9 @@ "node_type": "primitive", "type": "str" }, - "3": { - "value": null, + "bool": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -35,8 +39,9 @@ "node_type": "primitive", "type": "bool" }, - "4": { - "value": null, + "any": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -44,8 +49,9 @@ "node_type": "primitive", "type": "any" }, - "5": { - "value": null, + "none": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -53,7 +59,7 @@ "node_type": "primitive", "type": "none" }, - "6": { + "print_result": { "arguments": [ { "connection_type": "input", @@ -66,9 +72,9 @@ ], "outputs": [], "node_type": "function", - "method_name": "print_result" + "type": "print_result" }, - "7": { + "add": { "arguments": [ { "connection_type": "input", @@ -94,9 +100,9 @@ 2 ], "node_type": "function", - "method_name": "add" + "type": "add" }, - "8": { + "multiply": { "arguments": [ { "connection_type": "input", @@ -122,9 +128,9 @@ 2 ], "node_type": "function", - "method_name": "multiply" + "type": "multiply" }, - "9": { + "math.sqrt": { "arguments": [ { "connection_type": "input", @@ -144,9 +150,9 @@ 1 ], "node_type": "function", - "method_name": "math.sqrt" + "type": "math.sqrt" }, - "10": { + "math.sin": { "arguments": [ { "connection_type": "input", @@ -166,9 +172,9 @@ 1 ], "node_type": "function", - "method_name": "math.sin" + "type": "math.sin" }, - "11": { + "math.cos": { "arguments": [ { "connection_type": "input", @@ -188,9 +194,9 @@ 1 ], "node_type": "function", - "method_name": "math.cos" + "type": "math.cos" }, - "12": { + "math.pow": { "arguments": [ { "connection_type": "input", @@ -216,9 +222,9 @@ 2 ], "node_type": "function", - "method_name": "math.pow" + "type": "math.pow" }, - "13": { + "test_tuple_return": { "arguments": [ { "connection_type": "input", @@ -256,9 +262,9 @@ 4 ], "node_type": "function", - "method_name": "test_tuple_return" + "type": "test_tuple_return" }, - "14": { + "Calculator": { "arguments": [ { "connection_type": "input", @@ -275,11 +281,11 @@ "node_type": "constructor", "type": "Calculator" }, - "15": { + "Calculator.add_to_value": { "arguments": [ { "connection_type": "input", - "type": "Calculator", + "type": "any", "name": "self" }, { @@ -301,14 +307,13 @@ 2 ], "node_type": "method", - "method_name": "Calculator.add_to_value", "type": "Calculator.add_to_value" }, - "16": { + "Calculator.get_value": { "arguments": [ { "connection_type": "input", - "type": "Calculator", + "type": "any", "name": "self" }, { @@ -324,14 +329,13 @@ 1 ], "node_type": "method", - "method_name": "Calculator.get_value", "type": "Calculator.get_value" }, - "17": { + "Calculator.multiply_value": { "arguments": [ { "connection_type": "input", - "type": "Calculator", + "type": "any", "name": "self" }, { @@ -353,7 +357,6 @@ 2 ], "node_type": "method", - "method_name": "Calculator.multiply_value", "type": "Calculator.multiply_value" } } \ No newline at end of file diff --git a/tests/fixtures/valid_nodes/registry-phiflow.json b/tests/fixtures/valid_nodes/registry-phiflow.json index 5ca73fb..8ae1715 100644 --- a/tests/fixtures/valid_nodes/registry-phiflow.json +++ b/tests/fixtures/valid_nodes/registry-phiflow.json @@ -1,6 +1,7 @@ { - "0": { - "value": null, + "int": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -8,8 +9,9 @@ "node_type": "primitive", "type": "int" }, - "1": { - "value": null, + "float": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -17,8 +19,9 @@ "node_type": "primitive", "type": "float" }, - "2": { - "value": null, + "str": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -26,8 +29,9 @@ "node_type": "primitive", "type": "str" }, - "3": { - "value": null, + "bool": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -35,8 +39,9 @@ "node_type": "primitive", "type": "bool" }, - "4": { - "value": null, + "any": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -44,8 +49,9 @@ "node_type": "primitive", "type": "any" }, - "5": { - "value": null, + "none": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -53,7 +59,7 @@ "node_type": "primitive", "type": "none" }, - "6": { + "phiflow_iterate": { "arguments": [ { "connection_type": "input", @@ -115,9 +121,9 @@ 8 ], "node_type": "function", - "method_name": "phiflow_iterate" + "type": "phiflow_iterate" }, - "7": { + "phiflow_plot_and_save": { "arguments": [ { "connection_type": "input", @@ -167,9 +173,9 @@ 6 ], "node_type": "function", - "method_name": "phiflow_plot_and_save" + "type": "phiflow_plot_and_save" }, - "8": { + "phiflow_union": { "arguments": [ { "connection_type": "input", @@ -219,9 +225,9 @@ 6 ], "node_type": "function", - "method_name": "phiflow_union" + "type": "phiflow_union" }, - "9": { + "PhiFlowBox": { "arguments": [ { "connection_type": "input", @@ -244,7 +250,7 @@ "node_type": "constructor", "type": "PhiFlowBox" }, - "10": { + "PhiFlowSphere": { "arguments": [ { "connection_type": "input", @@ -273,7 +279,7 @@ "node_type": "constructor", "type": "PhiFlowSphere" }, - "11": { + "PhiFlowStaggeredGrid": { "arguments": [ { "connection_type": "input", @@ -302,7 +308,7 @@ "node_type": "constructor", "type": "PhiFlowStaggeredGrid" }, - "12": { + "PhiFlowCenteredGrid": { "arguments": [ { "connection_type": "input", @@ -331,7 +337,7 @@ "node_type": "constructor", "type": "PhiFlowCenteredGrid" }, - "13": { + "PhiFlowCuboid": { "arguments": [ { "connection_type": "input", @@ -366,11 +372,11 @@ "node_type": "constructor", "type": "PhiFlowCuboid" }, - "14": { + "PhiFlowBox.get_box": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowBox", + "type": "any", "name": "self" }, { @@ -386,14 +392,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box" }, - "15": { + "PhiFlowSphere.get_sphere": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowSphere", + "type": "any", "name": "self" }, { @@ -409,14 +414,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere" }, - "16": { + "PhiFlowStaggeredGrid.get_grid": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowStaggeredGrid", + "type": "any", "name": "self" }, { @@ -432,14 +436,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid" }, - "17": { + "PhiFlowCenteredGrid.get_grid": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowCenteredGrid", + "type": "any", "name": "self" }, { @@ -455,14 +458,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid" }, - "18": { + "PhiFlowCuboid.get_cuboid": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowCuboid", + "type": "any", "name": "self" }, { @@ -478,7 +480,6 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowCuboid.get_cuboid", "type": "PhiFlowCuboid.get_cuboid" } } \ No newline at end of file diff --git a/tests/fixtures/valid_nodes/registry-py.json b/tests/fixtures/valid_nodes/registry-py.json index 5ca73fb..dfdf8db 100644 --- a/tests/fixtures/valid_nodes/registry-py.json +++ b/tests/fixtures/valid_nodes/registry-py.json @@ -1,6 +1,7 @@ { - "0": { - "value": null, + "int": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -8,8 +9,9 @@ "node_type": "primitive", "type": "int" }, - "1": { - "value": null, + "float": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -17,8 +19,9 @@ "node_type": "primitive", "type": "float" }, - "2": { - "value": null, + "str": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -26,8 +29,9 @@ "node_type": "primitive", "type": "str" }, - "3": { - "value": null, + "bool": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -35,8 +39,9 @@ "node_type": "primitive", "type": "bool" }, - "4": { - "value": null, + "any": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -44,8 +49,9 @@ "node_type": "primitive", "type": "any" }, - "5": { - "value": null, + "none": { + "arguments": [], + "value": "", "inputs": [], "outputs": [ -1 @@ -53,7 +59,212 @@ "node_type": "primitive", "type": "none" }, - "6": { + "print_result": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "value" + } + ], + "inputs": [ + 0 + ], + "outputs": [], + "node_type": "function", + "type": "print_result" + }, + "add": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "a" + }, + { + "connection_type": "input", + "type": "float", + "name": "b" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "function", + "type": "add" + }, + "multiply": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "a" + }, + { + "connection_type": "input", + "type": "float", + "name": "b" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "function", + "type": "multiply" + }, + "math.sqrt": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "function", + "type": "math.sqrt" + }, + "math.sin": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "function", + "type": "math.sin" + }, + "math.cos": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "function", + "type": "math.cos" + }, + "math.pow": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "input", + "type": "float", + "name": "y" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "function", + "type": "math.pow" + }, + "test_tuple_return": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "x" + }, + { + "connection_type": "input", + "type": "float", + "name": "y" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2, + 3, + 4 + ], + "node_type": "function", + "type": "test_tuple_return" + }, + "phiflow_iterate": { "arguments": [ { "connection_type": "input", @@ -115,9 +326,9 @@ 8 ], "node_type": "function", - "method_name": "phiflow_iterate" + "type": "phiflow_iterate" }, - "7": { + "phiflow_plot_and_save": { "arguments": [ { "connection_type": "input", @@ -167,9 +378,9 @@ 6 ], "node_type": "function", - "method_name": "phiflow_plot_and_save" + "type": "phiflow_plot_and_save" }, - "8": { + "phiflow_union": { "arguments": [ { "connection_type": "input", @@ -219,9 +430,43 @@ 6 ], "node_type": "function", - "method_name": "phiflow_union" + "type": "phiflow_union" }, - "9": { + "Calculator": { + "arguments": [ + { + "connection_type": "input", + "type": "float", + "name": "initial_value" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + -1 + ], + "node_type": "constructor", + "type": "Calculator" + }, + "StringProcessor": { + "arguments": [ + { + "connection_type": "input", + "type": "str", + "name": "prefix" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + -1 + ], + "node_type": "constructor", + "type": "StringProcessor" + }, + "PhiFlowBox": { "arguments": [ { "connection_type": "input", @@ -244,7 +489,7 @@ "node_type": "constructor", "type": "PhiFlowBox" }, - "10": { + "PhiFlowSphere": { "arguments": [ { "connection_type": "input", @@ -273,7 +518,7 @@ "node_type": "constructor", "type": "PhiFlowSphere" }, - "11": { + "PhiFlowStaggeredGrid": { "arguments": [ { "connection_type": "input", @@ -302,7 +547,7 @@ "node_type": "constructor", "type": "PhiFlowStaggeredGrid" }, - "12": { + "PhiFlowCenteredGrid": { "arguments": [ { "connection_type": "input", @@ -331,7 +576,7 @@ "node_type": "constructor", "type": "PhiFlowCenteredGrid" }, - "13": { + "PhiFlowCuboid": { "arguments": [ { "connection_type": "input", @@ -366,11 +611,151 @@ "node_type": "constructor", "type": "PhiFlowCuboid" }, - "14": { + "Calculator.add_to_value": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "float", + "name": "amount" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "method", + "type": "Calculator.add_to_value" + }, + "Calculator.get_value": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0 + ], + "outputs": [ + 1 + ], + "node_type": "method", + "type": "Calculator.get_value" + }, + "Calculator.multiply_value": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowBox", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "float", + "name": "factor" + }, + { + "connection_type": "output", + "type": "float", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "method", + "type": "Calculator.multiply_value" + }, + "StringProcessor.concatenate": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "str", + "name": "text" + }, + { + "connection_type": "output", + "type": "str", + "name": "" + } + ], + "inputs": [ + 0, + 1 + ], + "outputs": [ + 2 + ], + "node_type": "method", + "type": "StringProcessor.concatenate" + }, + "StringProcessor.repeat": { + "arguments": [ + { + "connection_type": "input", + "type": "any", + "name": "self" + }, + { + "connection_type": "input", + "type": "str", + "name": "text" + }, + { + "connection_type": "input", + "type": "int", + "name": "times" + }, + { + "connection_type": "output", + "type": "str", + "name": "" + } + ], + "inputs": [ + 0, + 1, + 2 + ], + "outputs": [ + 3 + ], + "node_type": "method", + "type": "StringProcessor.repeat" + }, + "PhiFlowBox.get_box": { + "arguments": [ + { + "connection_type": "input", + "type": "any", "name": "self" }, { @@ -386,14 +771,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box" }, - "15": { + "PhiFlowSphere.get_sphere": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowSphere", + "type": "any", "name": "self" }, { @@ -409,14 +793,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere" }, - "16": { + "PhiFlowStaggeredGrid.get_grid": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowStaggeredGrid", + "type": "any", "name": "self" }, { @@ -432,14 +815,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid" }, - "17": { + "PhiFlowCenteredGrid.get_grid": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowCenteredGrid", + "type": "any", "name": "self" }, { @@ -455,14 +837,13 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid" }, - "18": { + "PhiFlowCuboid.get_cuboid": { "arguments": [ { "connection_type": "input", - "type": "PhiFlowCuboid", + "type": "any", "name": "self" }, { @@ -478,7 +859,6 @@ 1 ], "node_type": "method", - "method_name": "PhiFlowCuboid.get_cuboid", "type": "PhiFlowCuboid.get_cuboid" } } \ No newline at end of file diff --git a/tests/fixtures/valid_workflows/network-from-fe-classes.json b/tests/fixtures/valid_workflows/network-from-fe-classes.json index d6d3c29..4461dc1 100644 --- a/tests/fixtures/valid_workflows/network-from-fe-classes.json +++ b/tests/fixtures/valid_workflows/network-from-fe-classes.json @@ -2,116 +2,36 @@ "workflow": { "nodes": { "1": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "Calculator", - "is_valid": true, "position": { "x": 727.0088567094674, "y": 150.6169077927891 } }, "2": { - "value": "2", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "2", "position": { "x": 409.10272404418413, "y": 165.44154610360545 } }, "3": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [], - "node_type": "function", - "method_name": "print_result", - "is_valid": true, + "type": "print_result", "position": { "x": 1166.9096267853934, "y": 652.1758463770764 } }, "4": { - "arguments": [ - { - "connection_type": "input", - "type": "Calculator" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "output", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - 2 - ], - "node_type": "method", - "method_name": "Calculator.multiply_value", "type": "Calculator.multiply_value", - "is_valid": true, "position": { "x": 828.2176929443744, "y": 618.0805715970432 } }, "5": { - "arguments": [ - { - "connection_type": "input", - "type": "Calculator" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "output", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - 2 - ], - "node_type": "method", - "method_name": "Calculator.add_to_value", "type": "Calculator.add_to_value", - "is_valid": true, "position": { "x": 909.3658422869764, "y": 345.2266807705117 @@ -160,4 +80,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-18T15:30:52.705Z" -} \ No newline at end of file +} diff --git a/tests/fixtures/valid_workflows/network-from-fe-functions.json b/tests/fixtures/valid_workflows/network-from-fe-functions.json index c7bf37f..3ff2b84 100644 --- a/tests/fixtures/valid_workflows/network-from-fe-functions.json +++ b/tests/fixtures/valid_workflows/network-from-fe-functions.json @@ -2,121 +2,45 @@ "workflow": { "nodes": { "0": { - "value": "3.0", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "3.0", "position": { "x": 304, "y": 211.5 } }, "1": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [], - "node_type": "function", - "method_name": "print_result", - "is_valid": true, + "type": "print_result", "position": { "x": 1296.4665983931754, "y": 434.97657339760724 } }, "2": { - "value": "2", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "2", "position": { "x": 333.5492862144865, "y": 369.84117824551794 } }, "3": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "output", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - 2 - ], - "node_type": "function", - "method_name": "add", - "is_valid": true, + "type": "add", "position": { "x": 653.5527639131286, "y": 237.56010539744705 } }, "5": { - "value": "4", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "4", "position": { "x": 593.2481571735668, "y": 508.9308357254747 } }, "6": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "output", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - 2 - ], - "node_type": "function", - "method_name": "multiply", - "is_valid": true, + "type": "multiply", "position": { "x": 965.7750020324723, "y": 322.1810858223159 @@ -159,4 +83,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-16T15:14:12.580Z" -} \ No newline at end of file +} diff --git a/tests/fixtures/valid_workflows/network-from-fe-math.json b/tests/fixtures/valid_workflows/network-from-fe-math.json index f6f03d3..22aaed0 100644 --- a/tests/fixtures/valid_workflows/network-from-fe-math.json +++ b/tests/fixtures/valid_workflows/network-from-fe-math.json @@ -2,83 +2,29 @@ "workflow": { "nodes": { "0": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "output", - "type": "float" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "function", - "method_name": "math.sqrt", - "is_valid": true, + "type": "math.sqrt", "position": { "x": 665.9375, "y": 244 } }, "1": { - "value": "2", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "2", "position": { "x": 357.15625, "y": 247.5 } }, "2": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "output", - "type": "float" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "function", - "method_name": "math.sin", - "is_valid": true, + "type": "math.sin", "position": { "x": 431.65625, "y": 393 } }, "3": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [], - "node_type": "function", - "method_name": "print_result", - "is_valid": true, + "type": "print_result", "position": { "x": 724.65625, "y": 401 @@ -109,4 +55,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-19T10:03:06.230Z" -} \ No newline at end of file +} diff --git a/tests/fixtures/valid_workflows/network-from-fe-obstacle.json b/tests/fixtures/valid_workflows/network-from-fe-obstacle.json index 1f25fdf..47f1fcb 100644 --- a/tests/fixtures/valid_workflows/network-from-fe-obstacle.json +++ b/tests/fixtures/valid_workflows/network-from-fe-obstacle.json @@ -2,621 +2,221 @@ "workflow": { "nodes": { "5": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowBox", - "is_valid": true, "position": { "x": 483.4727174716225, "y": 184.05883472872716 } }, "6": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowSphere", - "is_valid": true, "position": { "x": 458.98402571738666, "y": 369.59874101472593 } }, "7": { - "value": "100", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "100", "position": { "x": 74.15462712424085, "y": 150.26190116571414 } }, "8": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowBox" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box", - "is_valid": true, "position": { "x": 801.0935675991999, "y": 168.24891504329503 } }, "9": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowSphere" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere", - "is_valid": true, "position": { "x": 832.4179762522326, "y": 836.4774096490305 } }, "10": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowStaggeredGrid", - "is_valid": true, "position": { "x": 1151.5482661346518, "y": 230.24065979041802 } }, "11": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowCenteredGrid" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid", - "is_valid": true, "position": { "x": 1507.3103341913477, "y": 544.7909056494979 } }, "12": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCenteredGrid", - "is_valid": true, "position": { "x": 1131.9667948110864, "y": 505.77932363656043 } }, "13": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "50", "position": { "x": 124.55833747868672, "y": 312.7979999970585 } }, "14": { - "value": "5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "5", "position": { "x": 98.13827531375492, "y": 557.470749611427 } }, "15": { - "value": "9.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "9.5", "position": { "x": 115.36875063871042, "y": 441.4522157567264 } }, "17": { - "value": "200", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "200", "position": { "x": 791.9465764247823, "y": 538.6944933919557 } }, "18": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowStaggeredGrid" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid", - "is_valid": true, "position": { "x": 1531.110090079808, "y": 317.6923246916215 } }, "19": { - "value": "64", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "64", "position": { "x": 812.6765701426165, "y": 295.24741064678926 } }, "21": { - "value": "10", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "10", "position": { "x": 1499.7148116124572, "y": 709.7983806620434 } }, "22": { - "value": "0.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "0.5", "position": { "x": 1480.5794687992932, "y": 822.1774347342322 } }, "23": { - "value": "3", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "3", "position": { "x": 1477.096307426024, "y": 935.9834225263783 } }, "26": { - "value": "simulation.mp4", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "str", - "is_valid": true, + "value": "simulation.mp4", "position": { "x": 2118.0889514499736, "y": 787.0272760951017 } }, "27": { - "value": "15", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "15", "position": { "x": 2100.554907630377, "y": 991.5911206570653 } }, "28": { - "value": "150", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "150", "position": { "x": 2078.150296083115, "y": 1101.6659513023124 } }, "29": { - "value": "80", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "80", "position": { "x": 2126.8559733597726, "y": 891.2574254671499 } }, "30": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2, - 3 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCuboid", - "is_valid": true, "position": { "x": 1845.2782757657292, "y": -69.40145577936774 } }, "31": { - "value": "10", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "10", "position": { "x": 1342.6514565166847, "y": 123.01146445601489 } }, "32": { - "value": "15", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "15", "position": { "x": 1337.9650843120937, "y": 34.85694587715529 } }, "33": { - "value": "60", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "60", "position": { "x": 1349.2748722273707, "y": -71.8925147318547 } }, "34": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "50", "position": { "x": 1364.0050962339246, "y": -182.47323852263662 } }, "35": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6, - 7, - 8 - ], - "node_type": "function", - "method_name": "phiflow_iterate", - "is_valid": true, + "type": "phiflow_iterate", "position": { "x": 2251.03451168449, "y": 328.6950845977049 } }, "36": { - "value": null, - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "none", - "is_valid": true, + "value": null, "position": { "x": 1880.0668186576954, "y": -195.7905819243028 } }, "37": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "str" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6 - ], - "node_type": "function", - "method_name": "phiflow_plot_and_save", - "is_valid": true, + "type": "phiflow_plot_and_save", "position": { "x": 2730.199112269673, "y": 469.75123359264353 @@ -815,4 +415,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-30T13:14:24.159Z" -} \ No newline at end of file +} diff --git a/tests/fixtures/valid_workflows/network-from-fe-smoke_plume.json b/tests/fixtures/valid_workflows/network-from-fe-smoke_plume.json index 62e30bd..9e50fcc 100644 --- a/tests/fixtures/valid_workflows/network-from-fe-smoke_plume.json +++ b/tests/fixtures/valid_workflows/network-from-fe-smoke_plume.json @@ -2,529 +2,182 @@ "workflow": { "nodes": { "5": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowBox", - "is_valid": true, "position": { "x": 483.4727174716225, "y": 184.05883472872716 } }, "6": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowSphere", - "is_valid": true, "position": { "x": 458.98402571738666, "y": 369.59874101472593 } }, "7": { - "value": "100", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "100", "position": { "x": 74.15462712424085, "y": 150.26190116571414 } }, "8": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowBox" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box", - "is_valid": true, "position": { "x": 801.0935675991999, "y": 168.24891504329503 } }, "9": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowSphere" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere", - "is_valid": true, "position": { "x": 832.4179762522326, "y": 836.4774096490305 } }, "10": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowStaggeredGrid", - "is_valid": true, "position": { "x": 1151.5482661346518, "y": 230.24065979041802 } }, "11": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowCenteredGrid" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid", - "is_valid": true, "position": { "x": 1507.3103341913477, "y": 544.7909056494979 } }, "12": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCenteredGrid", - "is_valid": true, "position": { "x": 1131.9667948110864, "y": 505.77932363656043 } }, "13": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "50", "position": { "x": 124.55833747868672, "y": 312.7979999970585 } }, "14": { - "value": "5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "5", "position": { "x": 98.13827531375492, "y": 557.470749611427 } }, "15": { - "value": "9.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "9.5", "position": { "x": 115.36875063871042, "y": 441.4522157567264 } }, "17": { - "value": "200", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "200", "position": { "x": 791.9465764247823, "y": 538.6944933919557 } }, "18": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowStaggeredGrid" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid", - "is_valid": true, "position": { "x": 1531.110090079808, "y": 317.6923246916215 } }, "19": { - "value": "64", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "64", "position": { "x": 812.6765701426165, "y": 295.24741064678926 } }, "21": { - "value": "10", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "10", "position": { "x": 1514.471231282937, "y": 693.1624783778074 } }, "22": { - "value": "0.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "0.5", "position": { "x": 1489.4336733458965, "y": 820.8346739408987 } }, "23": { - "value": "3", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "3", "position": { "x": 1477.096307426024, "y": 935.9834225263783 } }, "26": { - "value": "simulation.mp4", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "str", - "is_valid": true, + "value": "simulation.mp4", "position": { "x": 2113.787085705926, "y": 768.385857870896 } }, "27": { - "value": "15", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "15", "position": { "x": 2100.554907630377, "y": 991.5911206570653 } }, "28": { - "value": "150", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "150", "position": { "x": 2096.7917143073205, "y": 1100.2319960542964 } }, "29": { - "value": "80", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "80", "position": { "x": 2111.0824656315986, "y": 882.6536939790549 } }, "30": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6, - 7, - 8 - ], - "node_type": "function", - "method_name": "phiflow_iterate", - "is_valid": true, + "type": "phiflow_iterate", "position": { "x": 2235.697848994144, "y": 355.3962066855224 } }, "31": { - "value": null, - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "none", - "is_valid": true, + "value": null, "position": { "x": 1795.621487432151, "y": 126.74630249093471 } }, "32": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "str" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6 - ], - "node_type": "function", - "method_name": "phiflow_plot_and_save", - "is_valid": true, + "type": "phiflow_plot_and_save", "position": { "x": 2697.8713479683825, "y": 353.6651086243268 @@ -699,4 +352,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-30T13:08:32.728Z" -} \ No newline at end of file +} diff --git a/tests/fixtures/valid_workflows/network-from-fe.json b/tests/fixtures/valid_workflows/network-from-fe.json index 4a7510b..21ab2b9 100644 --- a/tests/fixtures/valid_workflows/network-from-fe.json +++ b/tests/fixtures/valid_workflows/network-from-fe.json @@ -2,757 +2,250 @@ "workflow": { "nodes": { "5": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowBox", - "is_valid": true, "position": { "x": 483.4727174716225, "y": 184.05883472872716 } }, "6": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowSphere", - "is_valid": true, "position": { "x": 458.98402571738666, "y": 369.59874101472593 } }, "7": { - "value": "100", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "100", "position": { "x": 74.15462712424085, "y": 150.26190116571414 } }, "8": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowBox" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowBox.get_box", "type": "PhiFlowBox.get_box", - "is_valid": true, "position": { "x": 801.0935675991999, "y": 168.24891504329503 } }, "9": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowSphere" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowSphere.get_sphere", "type": "PhiFlowSphere.get_sphere", - "is_valid": true, "position": { "x": 832.4179762522326, "y": 836.4774096490305 } }, "10": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowStaggeredGrid", - "is_valid": true, "position": { "x": 1151.5482661346518, "y": 230.24065979041802 } }, "11": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowCenteredGrid" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowCenteredGrid.get_grid", "type": "PhiFlowCenteredGrid.get_grid", - "is_valid": true, "position": { "x": 1507.3103341913477, "y": 544.7909056494979 } }, "12": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - } - ], - "inputs": [ - 0, - 1, - 2 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCenteredGrid", - "is_valid": true, "position": { "x": 1131.9667948110864, "y": 505.77932363656043 } }, "13": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "50", "position": { "x": 124.55833747868672, "y": 312.7979999970585 } }, "14": { - "value": "5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "5", "position": { "x": 98.13827531375492, "y": 557.470749611427 } }, "15": { - "value": "9.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "9.5", "position": { "x": 115.36875063871042, "y": 441.4522157567264 } }, "17": { - "value": "200", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "200", "position": { "x": 791.9465764247823, "y": 538.6944933919557 } }, "18": { - "arguments": [ - { - "connection_type": "input", - "type": "PhiFlowStaggeredGrid" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0 - ], - "outputs": [ - 1 - ], - "node_type": "method", - "method_name": "PhiFlowStaggeredGrid.get_grid", "type": "PhiFlowStaggeredGrid.get_grid", - "is_valid": true, "position": { "x": 1531.110090079808, "y": 317.6923246916215 } }, "19": { - "value": "64", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "64", "position": { "x": 812.6765701426165, "y": 295.24741064678926 } }, "21": { - "value": "50", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "50", "position": { "x": 1499.7148116124572, "y": 709.7983806620434 } }, "22": { - "value": "0.5", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "0.5", "position": { "x": 1480.5794687992932, "y": 822.1774347342322 } }, "23": { - "value": "3", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "3", "position": { "x": 1477.096307426024, "y": 935.9834225263783 } }, "26": { - "value": "simulation.mp4", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "str", - "is_valid": true, + "value": "simulation.mp4", "position": { "x": 2118.0889514499736, "y": 787.0272760951017 } }, "27": { - "value": "15", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "15", "position": { "x": 2100.554907630377, "y": 991.5911206570653 } }, "28": { - "value": "150", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "150", "position": { "x": 2078.150296083115, "y": 1101.6659513023124 } }, "29": { - "value": "80", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "int", - "is_valid": true, + "value": "80", "position": { "x": 2126.8559733597726, "y": 891.2574254671499 } }, "30": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2, - 3 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCuboid", - "is_valid": true, "position": { "x": 1852.0522382646295, "y": -450.43684634250496 } }, "31": { - "value": "10", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "10", "position": { "x": 1379.908250260636, "y": -14.161276146714528 } }, "32": { - "value": "80", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "80", "position": { "x": 1381.995840554945, "y": -134.49211659535015 } }, "33": { - "value": "60", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "60", "position": { "x": 1384.8381753465967, "y": -239.54808657963508 } }, "34": { - "value": "40", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "40", "position": { "x": 1391.1009462295256, "y": -350.12881037041706 } }, "35": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6, - 7, - 8 - ], - "node_type": "function", - "method_name": "phiflow_iterate", - "is_valid": true, + "type": "phiflow_iterate", "position": { "x": 2251.03451168449, "y": 328.6950845977049 } }, "37": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "str" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "int" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6 - ], - "node_type": "function", - "method_name": "phiflow_plot_and_save", - "is_valid": true, + "type": "phiflow_plot_and_save", "position": { "x": 2730.199112269673, "y": 469.75123359264353 } }, "39": { - "arguments": [ - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "input", - "type": "any" - }, - { - "connection_type": "output", - "type": "any" - } - ], - "inputs": [ - 0, - 1, - 2, - 3, - 4, - 5 - ], - "outputs": [ - 6 - ], - "node_type": "function", - "method_name": "phiflow_union", - "is_valid": true, + "type": "phiflow_union", "position": { "x": 2230.6193779757805, "y": -327.8828506528571 } }, "40": { - "value": "30", - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "float", - "is_valid": true, + "value": "30", "position": { "x": 1366.9391593660037, "y": -478.60351625338683 } }, "41": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2, - 3 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCuboid", - "is_valid": true, "position": { "x": 1863.1319124104443, "y": -732.6271099621451 } }, "42": { - "value": null, - "inputs": [], - "outputs": [ - -1 - ], - "node_type": "primitive", "type": "none", - "is_valid": true, + "value": null, "position": { "x": 1846.1970061631937, "y": 114.11820240038219 } }, "43": { - "arguments": [ - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - }, - { - "connection_type": "input", - "type": "float" - } - ], - "inputs": [ - 0, - 1, - 2, - 3 - ], - "outputs": [ - -1 - ], - "node_type": "constructor", "type": "PhiFlowCuboid", - "is_valid": true, "position": { "x": 1854.664459286819, "y": -168.69473192870203 @@ -1035,4 +528,4 @@ "version": 1, "author": "dealiix-platform", "date_time_utc": "2025-12-29T11:49:24.553Z" -} \ No newline at end of file +} diff --git a/tests/test_executor.py b/tests/test_executor.py index 522f357..91dab2d 100644 --- a/tests/test_executor.py +++ b/tests/test_executor.py @@ -113,7 +113,7 @@ def test_simple_add_function(self, temp_workflow_file): "nodes": { "n1": {"node_type": "primitive", "type": "float", "value": 5.0}, "n2": {"node_type": "primitive", "type": "float", "value": 3.0}, - "n3": {"node_type": "function", "method_name": "add"} + "n3": {"type": "add"} }, "edges": { "e1": {"source": "n1", "target": "n3", "source_output": 0, "target_input": 0}, @@ -135,7 +135,7 @@ def test_multiply_function(self, temp_workflow_file): "nodes": { "n1": {"node_type": "primitive", "type": "float", "value": 4.0}, "n2": {"node_type": "primitive", "type": "float", "value": 2.5}, - "n3": {"node_type": "function", "method_name": "multiply"} + "n3": {"type": "multiply"} }, "edges": { "e1": {"source": "n1", "target": "n3", "source_output": 0, "target_input": 0}, @@ -157,9 +157,9 @@ def test_chained_functions(self, temp_workflow_file): "nodes": { "n1": {"node_type": "primitive", "type": "float", "value": 2.0}, "n2": {"node_type": "primitive", "type": "float", "value": 3.0}, - "n3": {"node_type": "function", "method_name": "add"}, + "n3": {"type": "add"}, "n4": {"node_type": "primitive", "type": "float", "value": 2.0}, - "n5": {"node_type": "function", "method_name": "multiply"} + "n5": {"type": "multiply"} }, "edges": { "e1": {"source": "n1", "target": "n3", "source_output": 0, "target_input": 0}, @@ -216,7 +216,7 @@ def test_calculator_method(self, temp_workflow_file): "n1": {"node_type": "primitive", "type": "float", "value": 10.0}, "n2": {"node_type": "constructor", "type": "Calculator"}, "n3": {"node_type": "primitive", "type": "float", "value": 5.0}, - "n4": {"node_type": "method", "method_name": "Calculator.add_to_value"} + "n4": {"type": "Calculator.add_to_value"} }, "edges": { "e1": {"source": "n1", "target": "n2", "source_output": 0, "target_input": 0}, @@ -243,7 +243,7 @@ def test_simple_dag(self, temp_workflow_file): "nodes": { "n1": {"node_type": "primitive", "type": "int", "value": 1}, "n2": {"node_type": "primitive", "type": "int", "value": 2}, - "n3": {"node_type": "function", "method_name": "add"} + "n3": {"type": "add"} }, "edges": { "e1": {"source": "n1", "target": "n3", "source_output": 0, "target_input": 0}, @@ -283,7 +283,7 @@ def test_input_order_matters(self, temp_workflow_file): "nodes": { "n1": {"node_type": "primitive", "type": "float", "value": 2.0}, "n2": {"node_type": "primitive", "type": "float", "value": 3.0}, - "n3": {"node_type": "function", "method_name": "math.pow"} + "n3": {"type": "math.pow"} }, "edges": { "e1": {"source": "n1", "target": "n3", "source_output": 0, "target_input": 0}, diff --git a/tests/test_integration.py b/tests/test_integration.py index f008b3e..a69f380 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -248,8 +248,8 @@ def test_workflow_with_wrong_module(self, workflow_files): results = executor.execute() # If it doesn't fail, at least check it ran assert isinstance(results, dict) - except (KeyError, AttributeError): - # Expected - missing PhiFlow functions + except (KeyError, AttributeError, ValueError): + # Expected - missing PhiFlow functions (ValueError: unknown node type for the module) pass except ImportError: pytest.skip("PhiFlow workflow file might not exist or have issues") diff --git a/tests/test_registry.py b/tests/test_registry.py index 10e4bcf..fb7407c 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -9,29 +9,16 @@ import json -def find_by_method_name(registry, method_name): - """Helper to find registry entry by method_name.""" - for node_data in registry.values(): - if node_data.get('method_name') == method_name: - return node_data - return None - - def find_by_type(registry, type_name): - """Helper to find registry entry by type (for primitives and constructors).""" + """Helper to find a registry entry by its type (the node identifier).""" for node_data in registry.values(): if node_data.get('type') == type_name: return node_data return None -def has_method_name(registry, method_name): - """Check if registry contains an entry with given method_name.""" - return find_by_method_name(registry, method_name) is not None - - def has_type(registry, type_name): - """Check if registry contains an entry with given type.""" + """Check if the registry contains an entry with the given type.""" return find_by_type(registry, type_name) is not None @@ -72,14 +59,14 @@ def test_generate_registry_math_module(self): registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) # Check for math functions - assert has_method_name(registry, 'add') - assert has_method_name(registry, 'multiply') - assert has_method_name(registry, 'math.pow') + assert has_type(registry, 'add') + assert has_type(registry, 'multiply') + assert has_type(registry, 'math.pow') # Check for Calculator class assert has_type(registry, 'Calculator') - assert has_method_name(registry, 'Calculator.add_to_value') - assert has_method_name(registry, 'Calculator.multiply_value') + assert has_type(registry, 'Calculator.add_to_value') + assert has_type(registry, 'Calculator.multiply_value') def test_generate_registry_phiflow_module(self): """Test registry generation for phiflow module.""" @@ -89,7 +76,7 @@ def test_generate_registry_phiflow_module(self): registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) # Check for PhiFlow definitions (using actual names from definitions) - assert has_type(registry, 'PhiFlowBox') or has_method_name(registry, 'phiflow_union') + assert has_type(registry, 'PhiFlowBox') or has_type(registry, 'phiflow_union') # Registry should not be empty assert len(registry) > 0 except ImportError: @@ -114,7 +101,7 @@ def test_generate_registry_multiple_modules(self): registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) # Should have both math and string entries - assert has_method_name(registry, 'add') # from math + assert has_type(registry, 'add') # from math assert has_type(registry, 'StringProcessor') # from string def test_generate_registry_empty_modules(self): @@ -128,7 +115,7 @@ def test_generate_registry_empty_modules(self): assert has_type(registry, 'float') # Should not have module-specific entries - assert not has_method_name(registry, 'add') + assert not has_type(registry, 'add') assert not has_type(registry, 'Calculator') @@ -157,7 +144,7 @@ def test_function_entry_structure(self): function_map = build_function_map(include=['math']) class_map = build_class_map(include=['math']) registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) - add_entry = find_by_method_name(registry, 'add') + add_entry = find_by_type(registry, 'add') assert add_entry is not None assert 'arguments' in add_entry @@ -191,7 +178,7 @@ def test_method_entry_structure(self): function_map = build_function_map(include=['math']) class_map = build_class_map(include=['math']) registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) - method_entry = find_by_method_name(registry, 'Calculator.add_to_value') + method_entry = find_by_type(registry, 'Calculator.add_to_value') assert method_entry is not None assert 'arguments' in method_entry @@ -208,7 +195,7 @@ def test_function_arguments_have_types(self): function_map = build_function_map(include=['math']) class_map = build_class_map(include=['math']) registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) - add_entry = find_by_method_name(registry, 'add') + add_entry = find_by_type(registry, 'add') assert add_entry is not None # Check arguments structure @@ -234,7 +221,7 @@ def test_save_registry_to_file(self, tmp_path): with open(output_file, 'r') as f: registry = json.load(f) - assert has_method_name(registry, 'add') + assert has_type(registry, 'add') assert has_type(registry, 'Calculator') def test_save_registry_default_filename(self, tmp_path, monkeypatch): @@ -325,7 +312,7 @@ def test_exclude_module(self): registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) # Should have math - assert has_method_name(registry, 'add') + assert has_type(registry, 'add') # Should not have string assert not has_type(registry, 'StringProcessor') @@ -337,7 +324,7 @@ def test_include_specific_module(self): registry = generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) # Should have math - assert has_method_name(registry, 'add') + assert has_type(registry, 'add') assert has_type(registry, 'Calculator') def test_primitives_not_excludable(self): @@ -349,4 +336,46 @@ def test_primitives_not_excludable(self): # Primitives should still be there even with no modules assert has_type(registry, 'int') assert has_type(registry, 'float') - assert has_type(registry, 'str') \ No newline at end of file + assert has_type(registry, 'str') + + +class TestPlatformRegistryFormat: + """Test the DealiiX-platform-native registry format: keyed by type, function types, required keys.""" + + def _math_registry(self): + """Build a math-module registry for these tests.""" + function_map = build_function_map(include=['math']) + class_map = build_class_map(include=['math']) + return generate_registry(function_map, list(PRIMITIVES_MAP.keys()), class_map) + + def test_registry_keyed_by_type(self): + """Every entry is keyed by its own `type`, and known entries are reachable by that key.""" + registry = self._math_registry() + + for key, entry in registry.items(): + assert entry['type'] == key + + assert 'int' in registry # primitive + assert 'add' in registry # function + assert 'Calculator' in registry # constructor + assert 'Calculator.add_to_value' in registry # method + + def test_functions_keyed_by_name(self): + """Function entries are keyed by their name via `type` (the sole identifier).""" + registry = self._math_registry() + add_entry = registry['add'] + + assert add_entry['node_type'] == 'function' + assert add_entry['type'] == 'add' + + def test_every_entry_has_platform_required_keys(self): + """Every entry carries the keys the platform's registry validator requires. + + The platform skips any entry missing `node_type`, `arguments`, `inputs`, or `outputs` — so + even primitives (which take no inputs) must include an empty `arguments` list. + """ + registry = self._math_registry() + + for key, entry in registry.items(): + for required in ('node_type', 'arguments', 'inputs', 'outputs'): + assert required in entry, f"{key} missing {required}" \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..26d3e2d --- /dev/null +++ b/uv.lock @@ -0,0 +1,796 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "coral-python" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "h5py" }, + { name = "jax" }, + { name = "phiflow" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[package.metadata] +requires-dist = [ + { name = "h5py" }, + { name = "jax" }, + { name = "phiflow" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest" }, + { name = "pytest-cov" }, +] + +[[package]] +name = "coverage" +version = "7.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/8b/adeb62ea8951f13c4c7fef2e7a85e1a06b499c8d8237ea589d496029e53f/coverage-7.15.0.tar.gz", hash = "sha256:9ac3fe7a1435986463eaa8ee253ae2f2a268709ba4ae5c7dd1f52a05391ad78f", size = 925362, upload-time = "2026-07-02T13:10:50.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/74/fd4c0901137c4f8d81a76ada99e43c65163b4c94a02ece107a4ec0c6b615/coverage-7.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b75ee5e8cb7575636ac598719b4307ac529ec8fcd79608a35c3cd4d4dada812d", size = 220838, upload-time = "2026-07-02T13:09:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2e/2347583467bd7f0402635101a916961915cc68fce652cd0db5f173ea04fc/coverage-7.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffb31267816b93b075302248cc1737506081b4f163df4401e9df1a6424aafabe", size = 221197, upload-time = "2026-07-02T13:09:03.617Z" }, + { url = "https://files.pythonhosted.org/packages/f0/17/99fa688541ae1d6e84543a0e544f83de0c944815b63e9e7b1ed411d15036/coverage-7.15.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e4d0bb73455bf97ab243a8f12c37c686ccf1c13bb614b7b85f1d062f06f42b2c", size = 252705, upload-time = "2026-07-02T13:09:05.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/02/6a95a5cd83b74839017ef9cf48d2d8c9ae60af919e17a3f336e6f9f1b7bd/coverage-7.15.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:20d9ccc4ebd0edc434d86dfd2a1dd2a8efa6b6b3073d0485a394fee86459ebb4", size = 255441, upload-time = "2026-07-02T13:09:06.559Z" }, + { url = "https://files.pythonhosted.org/packages/67/f2/406f6c57d600f68185942422c4c00f1a3255d60aee6e5fd961425cd9987e/coverage-7.15.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:20c8a976c365c8cb12f0cbd099508772ea41fb5fa80657a8506df0e11bd278c5", size = 256556, upload-time = "2026-07-02T13:09:08.197Z" }, + { url = "https://files.pythonhosted.org/packages/74/8e/d3fa48489c15ecdec1ba48fd61f68798555dddd2f6716f9ad42adeb1a2a9/coverage-7.15.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f948fd5ba1b9cbca91f0ae08b4c1ce2b139509149a435e2585d056d57d70bf01", size = 258815, upload-time = "2026-07-02T13:09:09.691Z" }, + { url = "https://files.pythonhosted.org/packages/47/2e/2d40ddd110462c6a2769677cf7f1c119a52b45f568978fc6c98e4cc0dd0f/coverage-7.15.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f58185f06edf6ad68ec9fb155d63ef650c82f3fbd7e1770e2867751fb13158f4", size = 253117, upload-time = "2026-07-02T13:09:11.212Z" }, + { url = "https://files.pythonhosted.org/packages/51/c0/310782f0d7c3cb2b5ac05ba8d205fe91f24a36f6bf3256098f1782181c38/coverage-7.15.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02adc79a920c73c647c5d117f55747df7f2de94571884758ce8bc58e04f0a796", size = 254475, upload-time = "2026-07-02T13:09:13.029Z" }, + { url = "https://files.pythonhosted.org/packages/86/f7/702da6c275f8ae6ade423d2877243122932c9b27f5403003b9ef8c927d12/coverage-7.15.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6eb7c300fbed667fd6e3588eba71c1904cdb06110ca6fdf908c26bdd88b8e382", size = 252619, upload-time = "2026-07-02T13:09:14.699Z" }, + { url = "https://files.pythonhosted.org/packages/fb/84/c5b15a7e5ecba4e56218d772d99fe80a63e63f8d11f12783723a6005ab45/coverage-7.15.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b5fb23fa2de9dce1f5c36c09066d8fcda16cd96e8e26686caa2d7cb9b567d65c", size = 256689, upload-time = "2026-07-02T13:09:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/95/2f/c8b07559b57701230c61b23a953858c052890c12ef568d81780c6c46e92e/coverage-7.15.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:cec79341dbe6281484024979976d0c7f22beae08b4a254655decd25d42cbe766", size = 252189, upload-time = "2026-07-02T13:09:17.828Z" }, + { url = "https://files.pythonhosted.org/packages/6b/80/6d2f049dd3fd3dbfd60b62ba6b2162a04009e2c002ce70b24cf3878dec7a/coverage-7.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6c664c5444b1d970b1b2a450e21fb19ee5c9cfdf151ded2dda37260031cca0da", size = 254059, upload-time = "2026-07-02T13:09:19.304Z" }, + { url = "https://files.pythonhosted.org/packages/ce/92/b0287a2c42031d25c628f815f89a3cd9f8268ee78bb1252c9356cda1c689/coverage-7.15.0-cp312-cp312-win32.whl", hash = "sha256:5f764a3fa339bde6b3aa97657f5a6a3a9451e4a5b4ea98a2892c773a43525f77", size = 222893, upload-time = "2026-07-02T13:09:20.812Z" }, + { url = "https://files.pythonhosted.org/packages/a9/69/e34c481915fecb499b3146975061dac528752e37706edc1804f32c822469/coverage-7.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:52f9a4d2c4c56c8848bc2f524916698354b0211488b38c49ad9ae54f6cafbff6", size = 223429, upload-time = "2026-07-02T13:09:22.315Z" }, + { url = "https://files.pythonhosted.org/packages/fe/98/6e878f0b571d32684ef3f38d7c03db241ca5b82a5da8a5391596a8f209c4/coverage-7.15.0-cp312-cp312-win_arm64.whl", hash = "sha256:31e5c3e70c85307ea35a12964e2e40f56ca2ee4b1c8c721ccf4609d17071080b", size = 222810, upload-time = "2026-07-02T13:09:23.812Z" }, + { url = "https://files.pythonhosted.org/packages/76/04/145a3748098bcc86b631a85408d2c3dc5c104e0bd86d605468239b25b6c4/coverage-7.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5be4caf3b28836f078abe700f8944dac4a65d78f16d6c600c89cb624e5535782", size = 220863, upload-time = "2026-07-02T13:09:25.371Z" }, + { url = "https://files.pythonhosted.org/packages/a4/5c/4ed55708fed2c64b63c9bc5715daef670872202101938869b7fe5d5fbb8f/coverage-7.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dd58ad1404704303ca8d4f4b8a1095e7cbc7040ef17a66df1e6619aa10176430", size = 221230, upload-time = "2026-07-02T13:09:26.897Z" }, + { url = "https://files.pythonhosted.org/packages/7b/19/3a80b97d3b2a5c77a01ae359c6bed20c13738fe3d9380f08616d4fec0281/coverage-7.15.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bbcbb317c2e5ded5b21104af81c29f391be2af98d065693ffbe8d23949b948e5", size = 252227, upload-time = "2026-07-02T13:09:28.543Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fa/b70062750686bd7da454da27927622f48bbac6990ac7a4c4a4653e7b0036/coverage-7.15.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:27f31ecb458da3f859aab3f15ada871eb7a7768807d88df4a9f186bb17737970", size = 254823, upload-time = "2026-07-02T13:09:30.177Z" }, + { url = "https://files.pythonhosted.org/packages/a9/09/dad6a75a2e561b9dc5086a8c5257a7591d584246f67e23e70d2995b89ab6/coverage-7.15.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13fb759be317fdc62e0f56bffdf61cfcb45c7761ad6b71e3e583e71a67ae753c", size = 256059, upload-time = "2026-07-02T13:09:31.979Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e7/b5d2941fa9564573d44b693a871ff3156f0c42cbefe977a09fa7fdc59971/coverage-7.15.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5cf007add5ab4bb8fa9f4c77e3732127c9e6cad501d7db43355fbfafca0be84", size = 258190, upload-time = "2026-07-02T13:09:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1d/8e895bcde3c57ccd46d896dda5f2b3d5df761a1b0c6c9d450d175dedc632/coverage-7.15.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc78d9843bd576fbe2118248258d485e968dc535f95ed504a7b0867ba9b51389", size = 252456, upload-time = "2026-07-02T13:09:35.765Z" }, + { url = "https://files.pythonhosted.org/packages/14/4c/f6997da343ddeb959be82c3b05322793f92c071ad45f7cb8a96336e2dd5f/coverage-7.15.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a263060f1de0b4b74b4e089c2a70b8003b3781c733329a9c8fd54995328f9950", size = 254192, upload-time = "2026-07-02T13:09:37.445Z" }, + { url = "https://files.pythonhosted.org/packages/17/27/a0bc09d032267b9da89d95a2d874cfbef2a5aebbf0e87cf7aba221d79a99/coverage-7.15.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c48decf16e0dfd5b049c7d5e82200c23c08126719142998d4f172444e3d0529e", size = 252153, upload-time = "2026-07-02T13:09:39.422Z" }, + { url = "https://files.pythonhosted.org/packages/54/c0/77fc233d9fba07b244c40948c53fe27308b8f21732fb3417f87fbd6fd992/coverage-7.15.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:08fb028000ed0aaa0a4cbdfbb98be7cb42f370db973fbbb469733505ab20e13e", size = 256310, upload-time = "2026-07-02T13:09:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/d5/24/601cecfb5825becacb8d45219a018a3b55b9dbaec624efdb0ea249d08be2/coverage-7.15.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb7dc0c3b7d8a1077abea0b8546ebc5e26d6ef6ecefc2f0f5ad2b8a53bdad837", size = 251974, upload-time = "2026-07-02T13:09:42.733Z" }, + { url = "https://files.pythonhosted.org/packages/47/1e/6f45e5a5b3d5484318d368702af6716b5ab8913b0428bec981a562fcf296/coverage-7.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cb3602054ccbe9f0d8c2dc04bbeba90d5719236e2cd06e042ddd6d3fc7b6e37", size = 253745, upload-time = "2026-07-02T13:09:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/4df027a77bd11d0e527f44c53557c76e54ad027413d0304252ea3a78d67e/coverage-7.15.0-cp313-cp313-win32.whl", hash = "sha256:0bf781da64326b677be344df505171435b6f58716108606621d5d27d964fff8b", size = 222902, upload-time = "2026-07-02T13:09:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/a0/10/0355894d34e231f2c5449e71287e81a50793a325df2e2b027b7bcd9dfd19/coverage-7.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:2c57a275078ee3fa185f83e400f765bc764a549de66d99b47881645cbd4ea629", size = 223444, upload-time = "2026-07-02T13:09:47.687Z" }, + { url = "https://files.pythonhosted.org/packages/06/ef/bb725f263befaaff851203ab338e68af15e195d7f7b5f323162532d9b6a8/coverage-7.15.0-cp313-cp313-win_arm64.whl", hash = "sha256:3812c61afc6685c7999b39320779ab8f43b7a3081fdb0def39976e56fbdb9a21", size = 222839, upload-time = "2026-07-02T13:09:49.717Z" }, + { url = "https://files.pythonhosted.org/packages/4f/9c/1e3ca54f72a3185ece06c58d871099898c48f0ed6430d17b6ab75f0d180a/coverage-7.15.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:41cb79af843222e11da87127ad0ecbfa878abadd0f770a4a99391a27d3887324", size = 220906, upload-time = "2026-07-02T13:09:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/09/37/f718613d83b274880382f6b67e78f3802549ae39b0b3e65ae5b5974df56e/coverage-7.15.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7d2008989ef8fe54188d3f3bfa2e3099b025af11e90a6a1b9e7dc433d04263d8", size = 221239, upload-time = "2026-07-02T13:09:53.138Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/22bae91e0b75445f68d365c7643ed0aa4880bbf77450ee74ca65bdae53a7/coverage-7.15.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:769e8ece11a596315ebf5aa7ec383aeeed016c091d2bf6363ffb996d41529092", size = 252286, upload-time = "2026-07-02T13:09:54.996Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/bec5e32aa508615d9d7a2790effb25fb4dc28606e995816afe400b25ece3/coverage-7.15.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:65a6b6164ee5c39e2f3803f314292d6c61a607ba7fee253d1e03c42dc3903502", size = 254789, upload-time = "2026-07-02T13:09:56.678Z" }, + { url = "https://files.pythonhosted.org/packages/17/29/0e865435b4354e4a7c03b1b7920046d31d0a273d55decefea27e011cb9bf/coverage-7.15.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75128817f95a5c45bb01d65fd2d8b9cb54bbe03d81608fb70e3e14b437ad56c2", size = 256135, upload-time = "2026-07-02T13:09:58.343Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/33a870b58a13325d62fc0a6c8f01fa0ff667cef60c7498e2382a147dfa18/coverage-7.15.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9887bb428fe2d4cd4bee89bac1a6c9932f484afd5b36fbd4ff6ea5f825bb1f5e", size = 258449, upload-time = "2026-07-02T13:10:00.057Z" }, + { url = "https://files.pythonhosted.org/packages/18/7b/6fffe596bf3ddba8462758d02c5dad730fd91055a6634aa2e4226229181a/coverage-7.15.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0bfc0be1f702042207a93a00523b1065ee1fe951e96edf311581c0bbc2e34888", size = 252313, upload-time = "2026-07-02T13:10:01.946Z" }, + { url = "https://files.pythonhosted.org/packages/58/1b/11468dd6c1676ab831a70cb9a8d4e198e8607fa0b7220ab918b73fe9bfbd/coverage-7.15.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f64627d55def5a43282d70e08396672692f77e4da610a5bb8bb4060b432b6859", size = 254142, upload-time = "2026-07-02T13:10:04.065Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/29328e21d16b1b95092c30dd700e08cf915bd3734f836df8f3bdb0e8fa9f/coverage-7.15.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2c6f0fa473003905c6d5bac328ee4eba9fbea654f15bc24b8a3274b23363fa99", size = 252108, upload-time = "2026-07-02T13:10:06.11Z" }, + { url = "https://files.pythonhosted.org/packages/9b/de/05ccfb990439655b35afbfd8e0d13fe66677565a7d4eb38c3f5ef2635e1c/coverage-7.15.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2bcf9afaf064172c6ec3c58a325a9957ad1178c05dd934e25f253321776e0676", size = 256385, upload-time = "2026-07-02T13:10:08.141Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/486828a3d2695ea7a2609f17ff572f6b01905e608379440a11da4b8dffbe/coverage-7.15.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:baf06bc987115d6fb938d403f7eab684a057766c490367999a2b71a6883110c6", size = 251923, upload-time = "2026-07-02T13:10:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/18/c7/03582b6715f078e5e558354c87616d945b9894cda2dace8e4009b17035e4/coverage-7.15.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f0405f2ff97b1c4c0e782cb32e02f32369bcf2e6b618b591d67e1ea754575dfe", size = 253580, upload-time = "2026-07-02T13:10:12.052Z" }, + { url = "https://files.pythonhosted.org/packages/db/dc/9e578bbaf2ecb4959a81b7e7601ad8cca772cba2892e8d144cb749b4a71a/coverage-7.15.0-cp314-cp314-win32.whl", hash = "sha256:ab282853ed5fbd64bbb162f19cb8fcb7087187508a6374b4f9c34ec1577c4e8f", size = 223107, upload-time = "2026-07-02T13:10:13.994Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3e/c8c3b75d8dbe0e35f7b0cc3ff5e949fc59500f70b21d0398813f66740664/coverage-7.15.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bb3040e9f4bbe26fcb0cd7cc85ac63e630d3f3a9c74f027abf4caa27e706663", size = 223597, upload-time = "2026-07-02T13:10:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/cd/bc/3cbc9fb036eb388519bccd521f783499c39b64256013fbc362782f196fe1/coverage-7.15.0-cp314-cp314-win_arm64.whl", hash = "sha256:346771144d34f7fa84ec28386f78e0f31653f33cf35e19d253d5b35f9e8201da", size = 223020, upload-time = "2026-07-02T13:10:17.844Z" }, + { url = "https://files.pythonhosted.org/packages/28/00/199c4a8d656dff63102577a056c0fce2ff6a79e40adac092fc986c49cbf1/coverage-7.15.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d34a010905fb6401324ba016b5da03d574967f7b21ce48ea41e66f0f1f95f641", size = 221638, upload-time = "2026-07-02T13:10:19.703Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8e/9d0092c96a3d3a26951ecc7020826aa57bcb1b119ca81acbba996884ab13/coverage-7.15.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:bb25d825d885ca8036795dacfc3924d33091fc76d71ebc99420c6b79e77d96fa", size = 221903, upload-time = "2026-07-02T13:10:21.514Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b4/c0ca3028f42c9a08e51feb4561ef1192e5de99797cd1db5b04590c215bda/coverage-7.15.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:94c9686bfe8a9a6810297aecbd99beaa3445f9e8dc2f80b1382cca0d86b64461", size = 263267, upload-time = "2026-07-02T13:10:23.261Z" }, + { url = "https://files.pythonhosted.org/packages/5f/aa/a375e3846e5d3c013dc600b2a3231089055c73d77f5393dd2192a8d64da6/coverage-7.15.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bd671c25f9d85f09d7ec481d0e43d5139f486c06a37139847a7ce569788af72", size = 265390, upload-time = "2026-07-02T13:10:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/92/e1/5783cdabb797305e1c9e4809fea496d31834c51fa772514f73dc148bcfc9/coverage-7.15.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:110cbdf8d2e216577312cf06ccf85539c0e5a5420ef747e4a4719b5e483c88cd", size = 267811, upload-time = "2026-07-02T13:10:27.249Z" }, + { url = "https://files.pythonhosted.org/packages/85/31/96d8bbf58b8e9193bc8389574a91a0db48355ee98feb66aa6bf8d1b32eea/coverage-7.15.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c5d4619214f1d9993e7b00a8600d14614b7e9d84e89507460b126aa5e6559e5", size = 268928, upload-time = "2026-07-02T13:10:29.242Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7a/5294567e811a1cb7eda93140c628fa050d66189da28da320f93d1d815c73/coverage-7.15.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:781a704516e2d8346fbbd5be6c6f3412dd824785146528b3a01816f26c081007", size = 262378, upload-time = "2026-07-02T13:10:31.107Z" }, + { url = "https://files.pythonhosted.org/packages/69/3f/3f48538421f899f28946f90a3d272136a4686e1abf461cc9249a783ee0f3/coverage-7.15.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bd4a1b44bcb65ee29e947ac92bbee04956df3a6bfc6143641bb6cae7ede00fc9", size = 265263, upload-time = "2026-07-02T13:10:32.942Z" }, + { url = "https://files.pythonhosted.org/packages/ce/d3/092df15efcab8a9c1467ee960eb8019bbad3f9300d115d89ea6195f369ff/coverage-7.15.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0e4950c9d6d3e39c64c991814ff315e2d0b9cb8152363594212c9e55208c0a8f", size = 262866, upload-time = "2026-07-02T13:10:35.104Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ab/0254d2b88665efb2c57ad368cc77ab5de3435bd8d5add4729c1b0e79431e/coverage-7.15.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:fe9c87ff42e5472d80d21704972e1f96e104a0a599d77c5e35db5a3c562e2571", size = 266599, upload-time = "2026-07-02T13:10:37.05Z" }, + { url = "https://files.pythonhosted.org/packages/a8/79/1cfa4023e489ce6fbc7be4a5d442dbc375edb4f4fda39a352cedb53263c2/coverage-7.15.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f00d5ae1dd2fe13fb8186e3e7d37bcbd8b25c0d764ff7d1b32cef9be058510a8", size = 261714, upload-time = "2026-07-02T13:10:38.966Z" }, + { url = "https://files.pythonhosted.org/packages/b7/eb/fee5c8665656be63f497418d410484637c438172568688e8ac92e06574e7/coverage-7.15.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:363ab38cc78b615f11c9cac3cf1d7eef950c18b9fdedfb9066f59461dcf84d68", size = 264025, upload-time = "2026-07-02T13:10:40.789Z" }, + { url = "https://files.pythonhosted.org/packages/ab/99/63005db722f91edc81abc16302f9cc2f6228c1679e46e15be9ae144b14d0/coverage-7.15.0-cp314-cp314t-win32.whl", hash = "sha256:54fd9c53a5fafff509195f1b6a3f9be615d8e8362a3629ff1de23d270c03c86b", size = 223413, upload-time = "2026-07-02T13:10:42.597Z" }, + { url = "https://files.pythonhosted.org/packages/c1/e8/2bc6181c4fb06f1a6b981eb85330cc57bfad7e3f710fc9c9d350013ba228/coverage-7.15.0-cp314-cp314t-win_amd64.whl", hash = "sha256:87b47553097ba185ed964866078e7e63adea9f5f51b5f39691c34f30afd21080", size = 224245, upload-time = "2026-07-02T13:10:44.47Z" }, + { url = "https://files.pythonhosted.org/packages/79/b8/4d959bf9cc45d0cfed2f4d35cafcab978cdb6ea02eb5100009cd740632a3/coverage-7.15.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aeefb2dd178fe7eee79f0ad25d75855cb35ee9ed472db2c5ea06f5b4fd00cec5", size = 223558, upload-time = "2026-07-02T13:10:46.368Z" }, + { url = "https://files.pythonhosted.org/packages/52/30/21b2ad45959cd50e909e02ebac1e30b4ceb7162e91c11d4c570223a458b7/coverage-7.15.0-py3-none-any.whl", hash = "sha256:56da6a4cbe8f7e9e80bd072ca9cefe67d7106a440a7ec06519ec6507ac94ad19", size = 212632, upload-time = "2026-07-02T13:10:48.641Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "h5py" +version = "3.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526, upload-time = "2026-03-06T13:49:08.07Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604, upload-time = "2026-03-06T13:48:04.198Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940, upload-time = "2026-03-06T13:48:05.783Z" }, + { url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852, upload-time = "2026-03-06T13:48:07.482Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250, upload-time = "2026-03-06T13:48:09.628Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108, upload-time = "2026-03-06T13:48:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216, upload-time = "2026-03-06T13:48:13.322Z" }, + { url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868, upload-time = "2026-03-06T13:48:15.759Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286, upload-time = "2026-03-06T13:48:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808, upload-time = "2026-03-06T13:48:19.737Z" }, + { url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837, upload-time = "2026-03-06T13:48:21.854Z" }, + { url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860, upload-time = "2026-03-06T13:48:24.071Z" }, + { url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417, upload-time = "2026-03-06T13:48:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214, upload-time = "2026-03-06T13:48:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598, upload-time = "2026-03-06T13:48:29.49Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509, upload-time = "2026-03-06T13:48:31.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362, upload-time = "2026-03-06T13:48:33.411Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608, upload-time = "2026-03-06T13:48:35.183Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773, upload-time = "2026-03-06T13:48:37.139Z" }, + { url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886, upload-time = "2026-03-06T13:48:38.879Z" }, + { url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883, upload-time = "2026-03-06T13:48:41.324Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039, upload-time = "2026-03-06T13:48:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526, upload-time = "2026-03-06T13:48:44.838Z" }, + { url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263, upload-time = "2026-03-06T13:48:47.117Z" }, + { url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450, upload-time = "2026-03-06T13:48:48.707Z" }, + { url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693, upload-time = "2026-03-06T13:48:50.453Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305, upload-time = "2026-03-06T13:48:52.221Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061, upload-time = "2026-03-06T13:48:54.089Z" }, + { url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216, upload-time = "2026-03-06T13:48:56.75Z" }, + { url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068, upload-time = "2026-03-06T13:48:59.169Z" }, + { url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253, upload-time = "2026-03-06T13:49:02.033Z" }, + { url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671, upload-time = "2026-03-06T13:49:04.351Z" }, + { url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706, upload-time = "2026-03-06T13:49:06.347Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jax" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaxlib" }, + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "opt-einsum" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/73/eb91d98fcadfa2cbcfdd4e417ab116e47eb20882acc5ee678e47c35d6b57/jax-0.10.2.tar.gz", hash = "sha256:bf77428a8c2e6904c4f46d5ab12aa5cfc6cad2179f07f7e4c0fc75ac86ef0639", size = 2775110, upload-time = "2026-06-17T23:44:57.818Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/82/5ab5211079a151b6f661529369c0c8e98ec64cabf5c0cf22a0a05af124d8/jax-0.10.2-py3-none-any.whl", hash = "sha256:724d73c4678d8b06f6a6ab4db1b8a2fea8cd4f1e2c2564f99601634ec7b8d1c6", size = 3219515, upload-time = "2026-06-17T23:42:41.259Z" }, +] + +[[package]] +name = "jaxlib" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "scipy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/93/ee9cc8743191544f65d26ab7eeb82d65968fe60905662d1a5554d056654b/jaxlib-0.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47bb7c011515ea862be7e8313f40f9c56cbec09dc98a0fcb5016785fcd454c01", size = 61434612, upload-time = "2026-06-17T23:43:57.808Z" }, + { url = "https://files.pythonhosted.org/packages/11/06/8cc36021bf74d617c312eeed94c280282bb1bcbb32b63f2a42b10ae41575/jaxlib-0.10.2-cp312-cp312-manylinux_2_27_aarch64.whl", hash = "sha256:53b72977ae582c03a9e8e1cdee1efbf8ebc1418270965b0e69eade57acf40331", size = 81085366, upload-time = "2026-06-17T23:44:01.067Z" }, + { url = "https://files.pythonhosted.org/packages/48/17/38b718af2353dba7753300871e83fbb64a88a772e12727ae27373ab675ce/jaxlib-0.10.2-cp312-cp312-manylinux_2_27_x86_64.whl", hash = "sha256:fe88ec443714c4379968b6c109f9fa617c7ad19b802828e4d7bf861cd66da4b7", size = 85467828, upload-time = "2026-06-17T23:44:04.238Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c2/d41d13826ebdfe62e56cd87ba70fab3bb9fcbea4a6c9086739a91667e5bf/jaxlib-0.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:4b08f5fbc596b83f76308181863996f93d901d1f09cfd4e130a65c1998e1b371", size = 65900139, upload-time = "2026-06-17T23:44:07.476Z" }, + { url = "https://files.pythonhosted.org/packages/c2/68/eaa4cebe253359196a8e80a33b242959e27d8d2a6ae3d09339f21da2acb8/jaxlib-0.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4df530afa354a22dc1747a5d560640450cbb895d49889338a3f58c76a4c76c8e", size = 61434805, upload-time = "2026-06-17T23:44:10.511Z" }, + { url = "https://files.pythonhosted.org/packages/25/c1/4b884ea5962b6beb3c0f93742db54246bbf8b3274e48b0aca47908e454be/jaxlib-0.10.2-cp313-cp313-manylinux_2_27_aarch64.whl", hash = "sha256:45b28b0238697ab74bbcf20411aafb6db42acc31836cc2fd711e5cf056bf9556", size = 81084260, upload-time = "2026-06-17T23:44:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/36/ac/4ee28c65861605945223145fbcd3c9362ec2255ddf7d917574e205548c82/jaxlib-0.10.2-cp313-cp313-manylinux_2_27_x86_64.whl", hash = "sha256:9e4818b4a8756fd3918766ca2aa5342125809f4f08a6fe46026d4386e7c23644", size = 85467706, upload-time = "2026-06-17T23:44:17.471Z" }, + { url = "https://files.pythonhosted.org/packages/79/54/9918b0f77a25a1299818c0610305ca2bea38ed90584f4489b60357e2dd39/jaxlib-0.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:c75d6f1df1c9cff08e110b4a21c79560fdc502f4288972d6b117d25dafd44352", size = 65897894, upload-time = "2026-06-17T23:44:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/56/5b/70df11da52a8b1a826184cccc05a3fec8aed76058a980021873fba3069cb/jaxlib-0.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4c202d8ff7c1f3b5049dbd8f1e30e52759cd4e0a5835f0b3c7ae076a05818e28", size = 61564746, upload-time = "2026-06-17T23:44:24.421Z" }, + { url = "https://files.pythonhosted.org/packages/23/5c/184a648ea5db6c8b1a08fc5784c157b4c557255e009fb56091393df3c6de/jaxlib-0.10.2-cp313-cp313t-manylinux_2_27_aarch64.whl", hash = "sha256:b7b029bb95d981566750475b9719a9d6b66ed5dd2748851667899b6cfe075299", size = 81204888, upload-time = "2026-06-17T23:44:27.57Z" }, + { url = "https://files.pythonhosted.org/packages/6c/5c/539596a55265711d74147913278bcdc38412980be7d74d9c9d860297c486/jaxlib-0.10.2-cp313-cp313t-manylinux_2_27_x86_64.whl", hash = "sha256:e8b126097d609b0c6e6786e89f6dd6978adc02ebd5f63a1c61293fbac7821305", size = 85583810, upload-time = "2026-06-17T23:44:30.962Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/27471ec9f1d04674f6e62de809412371e097aed3eca7d9483e677c54c214/jaxlib-0.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:72eba28b12fee02616fa42aa4b881b4ab62d7757c7843c462401d3fb34a27be4", size = 61446097, upload-time = "2026-06-17T23:44:34.196Z" }, + { url = "https://files.pythonhosted.org/packages/af/c8/941a7f7f37510f51290a5bd1a413aeef977fb8ba8adc0cfe8391233a764c/jaxlib-0.10.2-cp314-cp314-manylinux_2_27_aarch64.whl", hash = "sha256:f18f56fee90699cfba9b6627045a7a299702cb0e2af82ce180d9a6a7c8048093", size = 81096546, upload-time = "2026-06-17T23:44:37.486Z" }, + { url = "https://files.pythonhosted.org/packages/69/77/ac054882c220872512df28d16aeb648fe0e651efbb5be4fd7c4817fd88b0/jaxlib-0.10.2-cp314-cp314-manylinux_2_27_x86_64.whl", hash = "sha256:ca34f363197fb0ac4082582ca755007910369e33f8a8ba3d35ed94b71070107d", size = 85472993, upload-time = "2026-06-17T23:44:40.904Z" }, + { url = "https://files.pythonhosted.org/packages/0d/7d/c592d1fa69c210be0d2743fffc598dfc2f54efa9671c5f6f5d1e151c6f4a/jaxlib-0.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:99818b0a18adc0b899abf4873795e8d65169441d87ab2e5cbb228e73d0f25808", size = 68376553, upload-time = "2026-06-17T23:44:44.968Z" }, + { url = "https://files.pythonhosted.org/packages/54/9b/91b00ec74985d29708b50420b4103c1f651c8f1c253d4fcb49d1bbb532cd/jaxlib-0.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fc62997fce8831819551a2a5469a818169b09582b5b648c102d11ac7205bb812", size = 61564620, upload-time = "2026-06-17T23:44:48.217Z" }, + { url = "https://files.pythonhosted.org/packages/a1/c7/49d2b19c3b3105c30e1d3af2062e82e1977fb239d3dc3cbb583ef676dda7/jaxlib-0.10.2-cp314-cp314t-manylinux_2_27_aarch64.whl", hash = "sha256:a24d6e3cba263978293eae8b41330d5ccf24d6cdd1a6bcd4e82aff34e767620d", size = 81206365, upload-time = "2026-06-17T23:44:51.419Z" }, + { url = "https://files.pythonhosted.org/packages/bf/99/006cedf443f4a01f2088651facce79b2105bfb4905bfe9162eb0920a6dfb/jaxlib-0.10.2-cp314-cp314t-manylinux_2_27_x86_64.whl", hash = "sha256:5a2ac7aed7c4e661f67600bbcdec9e589151c1efec91f4cdb8d484af1a45c895", size = 85584458, upload-time = "2026-06-17T23:44:55.377Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1f/24/080c99d223d158d3a8902769269ab6da5b50f7a0e6e072513907e02b7a6c/matplotlib-3.11.0.tar.gz", hash = "sha256:68c0c7be01b30dcca3638934f7f591df73401235cbdbf0d1ab1c71e7db7f8b57", size = 33251176, upload-time = "2026-06-12T02:29:15.508Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/17/f5276b496c61477a6c4fc5e7401f4bfe1c2e5ef7c6cd67896f2ade3809cb/matplotlib-3.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06b5872e9cf11adc8f589ded3ce11bc3e1061ad498259664fabc1f6615beb918", size = 9449976, upload-time = "2026-06-12T02:27:50.989Z" }, + { url = "https://files.pythonhosted.org/packages/82/34/bdd77418adb2178a1d59f044bd67bfebb115896e91b840b8a197eb3f4f4e/matplotlib-3.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0515d495124be3124340e59f164d901ed4484e2246a5b74cfa483cac3b80bd97", size = 9279307, upload-time = "2026-06-12T02:27:53.247Z" }, + { url = "https://files.pythonhosted.org/packages/94/95/7f522393c88313336b20d70fc849555757b2e5febc22b83b3a3f0fd4bce9/matplotlib-3.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be5f93a1d21981bfb802ded0d77a0caa92d4342a47d45754fac77e314a506344", size = 10031353, upload-time = "2026-06-12T02:27:55.215Z" }, + { url = "https://files.pythonhosted.org/packages/87/ce/8f25a0e3186aefd61913e7467d1b999465bcd0d0c03ac695c1b26ca559b7/matplotlib-3.11.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41635d7909d19e52e924a521dde6d8f670b0f53ab1d0e8c331fa831554f681d1", size = 10839232, upload-time = "2026-06-12T02:27:57.746Z" }, + { url = "https://files.pythonhosted.org/packages/85/c2/db15da2bbdf9e3ca66df7db8e2c33a1dfed67be24a24d2c878efaaff01d6/matplotlib-3.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:94f5000f67ca9faa300863ea17f8bce9175cb67b88bec4bc7780502d53dd7c9e", size = 10923899, upload-time = "2026-06-12T02:28:00.223Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2f/a58a4443a4d052a4ea77557478336aefc26c7981f6408d37adba763aa758/matplotlib-3.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ac6f1ef39f3d0f9e2463303013094992cdbe0f85f43bc54155bc472b2042768e", size = 9329528, upload-time = "2026-06-12T02:28:02.27Z" }, + { url = "https://files.pythonhosted.org/packages/61/0f/4b669589d47733b97ab9df4b58d6fc1e68acb5ea42a928dc7cbdd6bf5871/matplotlib-3.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:9dd11fb612ce7bc60b1de5b4fc87ff959d22317b5de42aabf392f66f97af22eb", size = 9003413, upload-time = "2026-06-12T02:28:04.49Z" }, + { url = "https://files.pythonhosted.org/packages/55/41/aa47f156b061d14c98b906f76c428507397708ec63ff94f410ae1752b426/matplotlib-3.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce3b839b34ae1f430b4616893a2945a2999debaa7e94e7e29a2a8bbf286f7b5", size = 9450532, upload-time = "2026-06-12T02:28:06.769Z" }, + { url = "https://files.pythonhosted.org/packages/8c/4f/5a9eb0375e81413953febf8af7b012a6b6357f53438a15c4f5ad86c6bbb5/matplotlib-3.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:373db8f91214e8ccaf35ac833cc1dd59dd961e148bbd55dd027141591dde1313", size = 9279760, upload-time = "2026-06-12T02:28:09.152Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c0/1117d53077e3ac3152503a84e9cf7a5c239576805ee71276e80c2aaa7471/matplotlib-3.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be152b7570324dc8d01574cc9474dd2d803237acf528bcbb5b211fa347461a09", size = 10031623, upload-time = "2026-06-12T02:28:11.26Z" }, + { url = "https://files.pythonhosted.org/packages/92/7e/e937138daffad65b71bf831a377809dcbc830fb4f31a31e067dc1faa2575/matplotlib-3.11.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:126f256df600652d7e4b394cf3164ff75210a00038f287c95a012a6f58d0e83f", size = 10839372, upload-time = "2026-06-12T02:28:14.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/c2/438ecc197ffb8023b6b9922915542f2172f5fd45b76703b0b4fc47322243/matplotlib-3.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:03acfeddf87b0dddb11b081ef7740ad445a3ca8bcb6b8e3011b08f2cf802b75c", size = 10924099, upload-time = "2026-06-12T02:28:16.383Z" }, + { url = "https://files.pythonhosted.org/packages/40/2e/395883da416f378b3ed2c9f3e843ac477eae1ce731b671b79adaa6f0bacd/matplotlib-3.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:ab3722f04f3ff34c23b5012c5873d2894174e06c3822fcdac3610965a5ac7d06", size = 9329727, upload-time = "2026-06-12T02:28:18.581Z" }, + { url = "https://files.pythonhosted.org/packages/61/82/2c388956abf8bf392dfb5b8917c502f1082df6a941b781ab8c8e5ba2474b/matplotlib-3.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c945824670fb8915b4ac879e5e61f3c58e0913022f70a0de4c082b17372f8771", size = 9003506, upload-time = "2026-06-12T02:28:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c1/34454baa44da7975ada82e9aea37105ec47059514dc967d3be14426ba8dc/matplotlib-3.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3489c3dc487669b4a980bc3068f87856de7a1564248d3f6c629efb2a58b03f24", size = 9499838, upload-time = "2026-06-12T02:28:22.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c3/98fe79a398cf232219f090163a7fa7e6766e9f2e0ad26df54d6f8934d8ee/matplotlib-3.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6a98f5476ce784a50ce09998f4ae1e6a9f25043cef8a480c98949902eda74620", size = 9332298, upload-time = "2026-06-12T02:28:24.796Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/b4b7c33151e74e5c802f3cde1ba807ebfc38401e329b44e215a5888dd76d/matplotlib-3.11.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:565af866fd63e4bd3f987d580afe27c44c2552a3b3305f4ecbb85133601ea6f3", size = 10045491, upload-time = "2026-06-12T02:28:27.141Z" }, + { url = "https://files.pythonhosted.org/packages/71/28/394548efd68354110c1a1be11fe6b6e559e06d1a23da35908a0e316c55a9/matplotlib-3.11.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e6b3e64dea5062c570f04358e2711859f3531b459f29516274fbad889079e4f3", size = 10857059, upload-time = "2026-06-12T02:28:29.222Z" }, + { url = "https://files.pythonhosted.org/packages/c8/44/e7922e6e2a4d63bdfbc9dc4a53e3850ab438d46cf42e6779bb15ec92c948/matplotlib-3.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:942b37c5db1899610bd1543ce8e13e4ecff9a4633e7f63bb6aa9205d2644ebd1", size = 10939576, upload-time = "2026-06-12T02:28:31.66Z" }, + { url = "https://files.pythonhosted.org/packages/3d/be/b1ca96003a441d619b727fee21d671fdff7a5ce2f1bb797b2521aa2f679a/matplotlib-3.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:c08e649a6313e1291e713623b97a38e5bb4aa580b2a100a94a3309bc6b9c8eb3", size = 9379519, upload-time = "2026-06-12T02:28:33.888Z" }, + { url = "https://files.pythonhosted.org/packages/e3/72/4bf3b91821c34596dd6a7bdac5836d94f744144c8208939ef49d8ec43f7e/matplotlib-3.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2746cd2c113742ff6ce37a864c5ac5fd7aa644568f445e66166e457ac78e40e0", size = 9055456, upload-time = "2026-06-12T02:28:35.878Z" }, + { url = "https://files.pythonhosted.org/packages/57/52/a94102ac99eb78e2fe9b826674f9ef9ee23327110ea6ab4776c1b4eb6209/matplotlib-3.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3338e3e3de128cf50d0d2fb92a122815daf9c755bd882a474343c05f8fd7ec79", size = 9452137, upload-time = "2026-06-12T02:28:37.93Z" }, + { url = "https://files.pythonhosted.org/packages/7c/03/b8cdb625a21f710dfa11bbca1f48fb4057d2c0286975f8b415bf80942c99/matplotlib-3.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:25c2e5455efd8d99f41fb79871a31feb7d301569642e332ec58d72cfe9282bc3", size = 9281514, upload-time = "2026-06-12T02:28:40.028Z" }, + { url = "https://files.pythonhosted.org/packages/b7/2d/4e1240ea82ee197dfb3851e71f71c87eeeb975f1753b56a0588e4e80739a/matplotlib-3.11.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9695457a467ff86d23f35037a43deb6f1134dd6d3e2ac8ce1e2087cff09ffb9", size = 10843005, upload-time = "2026-06-12T02:28:42.39Z" }, + { url = "https://files.pythonhosted.org/packages/29/dc/6377ecfaa5fef79430f74a1a16638b4e2aa30d4692bae2c19f9d76fe3b01/matplotlib-3.11.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19c16c61dea63b3582918503e6b294193961261d9daa806d4ae2151f1ad05430", size = 11127459, upload-time = "2026-06-12T02:28:44.483Z" }, + { url = "https://files.pythonhosted.org/packages/6f/41/795c405aa7560443a3b01309424cde4a1113b85c90b8a63417444a749617/matplotlib-3.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2d72ea8b7924f3cb955e61518d21e43b3df1e6c8a793b480a0c1214f185d30ba", size = 10925160, upload-time = "2026-06-12T02:28:46.564Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f7/3a9e6389a7cfaeff76c56e40c2dabcb13110e21e82f837228c834ebe748c/matplotlib-3.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:1c02da0a629dfa9debf52725ea06866b74c1fb70a895bae05e4493d34074f9f2", size = 9485186, upload-time = "2026-06-12T02:28:49.344Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c0/396478ee7cf2091d182db8b4a8695f6a37f1ddb978989cf9dbb84cd5c123/matplotlib-3.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:aa55d73b3117d4b07f959cd9eb6f69b375d8df3414139c479388e551aa5d999d", size = 9160349, upload-time = "2026-06-12T02:28:51.382Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6f/1c3bd51bb2b34eaacdcf3c3d859dbb357f952fc8020c617dc118ad7c9e38/matplotlib-3.11.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9d8c6e7cd2f0ddf11d8d92e520dd1d9d2abb0cf6ac8831e338666c81e905847", size = 9500921, upload-time = "2026-06-12T02:28:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/4d861d0121840cb1a3fd4a10deb211efd6fccd481ed23e553f31f4f4da4a/matplotlib-3.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:be050fcf32f729eda99f7f75a80bf67612ce16ab9ac1c23a387dcaede95cb70e", size = 9332190, upload-time = "2026-06-12T02:28:55.623Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/22f6bc35711a0b5639a784e74e653e77c86210bd4304449dd399a482f74e/matplotlib-3.11.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfabef0230d0697aa0d717385194dd41162e00207a68bf4abf94c2bf4c27dca0", size = 10854181, upload-time = "2026-06-12T02:28:57.856Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7e/9a9eaca731a2939589da520f0ebe8fd8753d0f51fca98c7d20af6dbe261a/matplotlib-3.11.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1644db30e759199443493ac5e5caec24fdb775a8f6123021f85ba47c4133c3cb", size = 11137715, upload-time = "2026-06-12T02:29:00.555Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f9/9b030b6088354acb0296871bb624b25befc1c42509d3c6cd17420c83a5b8/matplotlib-3.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15b0d160079cb10699a0e98b5989c70677b2df7cacdc62af67c30f2facec46d9", size = 10939427, upload-time = "2026-06-12T02:29:02.527Z" }, + { url = "https://files.pythonhosted.org/packages/59/94/6b273eaee4ee250863567d100865da61a5c1527fa67f527b7ed22e0dd29c/matplotlib-3.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:446307e6b04b57b1f1239e228a1ec2af0d589a1008cebc3dfa3f5441d095cfb6", size = 9535809, upload-time = "2026-06-12T02:29:04.994Z" }, + { url = "https://files.pythonhosted.org/packages/60/95/1d36bddf2b7e2692c1540e78a6e5bc88bc1496b137e3e35a611f91b65ac3/matplotlib-3.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:652fb5696271d4c50f196d22a5ff4f8e4444c74f847423570d7dc0aa2bbd0159", size = 9209226, upload-time = "2026-06-12T02:29:07.033Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, + { url = "https://files.pythonhosted.org/packages/d9/a1/4008f14bbc616cfb1ac5b39ea485f9c63031c4634ab3f4cf72e7541f816a/ml_dtypes-0.5.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48", size = 676888, upload-time = "2025-11-17T22:31:56.907Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b7/dff378afc2b0d5a7d6cd9d3209b60474d9819d1189d347521e1688a60a53/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b", size = 5036993, upload-time = "2025-11-17T22:31:58.497Z" }, + { url = "https://files.pythonhosted.org/packages/eb/33/40cd74219417e78b97c47802037cf2d87b91973e18bb968a7da48a96ea44/ml_dtypes-0.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d", size = 5010956, upload-time = "2025-11-17T22:31:59.931Z" }, + { url = "https://files.pythonhosted.org/packages/e1/8b/200088c6859d8221454825959df35b5244fa9bdf263fd0249ac5fb75e281/ml_dtypes-0.5.4-cp313-cp313-win_amd64.whl", hash = "sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328", size = 212224, upload-time = "2025-11-17T22:32:01.349Z" }, + { url = "https://files.pythonhosted.org/packages/8f/75/dfc3775cb36367816e678f69a7843f6f03bd4e2bcd79941e01ea960a068e/ml_dtypes-0.5.4-cp313-cp313-win_arm64.whl", hash = "sha256:35f29491a3e478407f7047b8a4834e4640a77d2737e0b294d049746507af5175", size = 160798, upload-time = "2025-11-17T22:32:02.864Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/e9ddb35fd1dd43b1106c20ced3f53c2e8e7fc7598c15638e9f80677f81d4/ml_dtypes-0.5.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6", size = 702083, upload-time = "2025-11-17T22:32:04.08Z" }, + { url = "https://files.pythonhosted.org/packages/74/f5/667060b0aed1aa63166b22897fdf16dca9eb704e6b4bbf86848d5a181aa7/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d", size = 5354111, upload-time = "2025-11-17T22:32:05.546Z" }, + { url = "https://files.pythonhosted.org/packages/40/49/0f8c498a28c0efa5f5c95a9e374c83ec1385ca41d0e85e7cf40e5d519a21/ml_dtypes-0.5.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298", size = 5366453, upload-time = "2025-11-17T22:32:07.115Z" }, + { url = "https://files.pythonhosted.org/packages/8c/27/12607423d0a9c6bbbcc780ad19f1f6baa2b68b18ce4bddcdc122c4c68dc9/ml_dtypes-0.5.4-cp313-cp313t-win_amd64.whl", hash = "sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6", size = 225612, upload-time = "2025-11-17T22:32:08.615Z" }, + { url = "https://files.pythonhosted.org/packages/e5/80/5a5929e92c72936d5b19872c5fb8fc09327c1da67b3b68c6a13139e77e20/ml_dtypes-0.5.4-cp313-cp313t-win_arm64.whl", hash = "sha256:3bbbe120b915090d9dd1375e4684dd17a20a2491ef25d640a908281da85e73f1", size = 164145, upload-time = "2025-11-17T22:32:09.782Z" }, + { url = "https://files.pythonhosted.org/packages/72/4e/1339dc6e2557a344f5ba5590872e80346f76f6cb2ac3dd16e4666e88818c/ml_dtypes-0.5.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22", size = 673781, upload-time = "2025-11-17T22:32:11.364Z" }, + { url = "https://files.pythonhosted.org/packages/04/f9/067b84365c7e83bda15bba2b06c6ca250ce27b20630b1128c435fb7a09aa/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465", size = 5036145, upload-time = "2025-11-17T22:32:12.783Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/82c7dcf38070b46172a517e2334e665c5bf374a262f99a283ea454bece7c/ml_dtypes-0.5.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f", size = 5010230, upload-time = "2025-11-17T22:32:14.38Z" }, + { url = "https://files.pythonhosted.org/packages/e9/93/2bfed22d2498c468f6bcd0d9f56b033eaa19f33320389314c19ef6766413/ml_dtypes-0.5.4-cp314-cp314-win_amd64.whl", hash = "sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56", size = 221032, upload-time = "2025-11-17T22:32:15.763Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/9c912fe6ea747bb10fe2f8f54d027eb265db05dfb0c6335e3e063e74e6e8/ml_dtypes-0.5.4-cp314-cp314-win_arm64.whl", hash = "sha256:5a0f68ca8fd8d16583dfa7793973feb86f2fbb56ce3966daf9c9f748f52a2049", size = 163353, upload-time = "2025-11-17T22:32:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/cd/02/48aa7d84cc30ab4ee37624a2fd98c56c02326785750cd212bc0826c2f15b/ml_dtypes-0.5.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9", size = 702085, upload-time = "2025-11-17T22:32:18.175Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e7/85cb99fe80a7a5513253ec7faa88a65306be071163485e9a626fce1b6e84/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7", size = 5355358, upload-time = "2025-11-17T22:32:19.7Z" }, + { url = "https://files.pythonhosted.org/packages/79/2b/a826ba18d2179a56e144aef69e57fb2ab7c464ef0b2111940ee8a3a223a2/ml_dtypes-0.5.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf", size = 5366332, upload-time = "2025-11-17T22:32:21.193Z" }, + { url = "https://files.pythonhosted.org/packages/84/44/f4d18446eacb20ea11e82f133ea8f86e2bf2891785b67d9da8d0ab0ef525/ml_dtypes-0.5.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1", size = 236612, upload-time = "2025-11-17T22:32:22.579Z" }, + { url = "https://files.pythonhosted.org/packages/ad/3f/3d42e9a78fe5edf792a83c074b13b9b770092a4fbf3462872f4303135f09/ml_dtypes-0.5.4-cp314-cp314t-win_arm64.whl", hash = "sha256:11942cbf2cf92157db91e5022633c0d9474d4dfd813a909383bd23ce828a4b7d", size = 168825, upload-time = "2025-11-17T22:32:23.766Z" }, +] + +[[package]] +name = "numpy" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/db/f4/731b6085a83faf6ca843394cbd5e217280c214399f7e8b21b9f552af0ae2/numpy-2.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:7c786fe9a5bbe360022e584c5a34cf6b54265c71bd7ec8ac3d8fec38968071f8", size = 16795063, upload-time = "2026-07-04T17:07:07.374Z" }, + { url = "https://files.pythonhosted.org/packages/bf/64/0e215f2048dd11a55bb989ed41b3585ef57452404e638d703a211a3e4157/numpy-2.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32985c896d897419ef8da6917872d80b78ad0ea26d85b23245c7366ffde76d75", size = 11776652, upload-time = "2026-07-04T17:07:09.907Z" }, + { url = "https://files.pythonhosted.org/packages/b5/59/2b844c7a6e9deff69b404a66221e1542937734f65d5e6e39411876053862/numpy-2.5.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:efd736408cc97c79b9e6917338dfc8f06013b2274f992e96b1d9a81a71e2a2c2", size = 5335944, upload-time = "2026-07-04T17:07:12.227Z" }, + { url = "https://files.pythonhosted.org/packages/86/51/9bf7cb2cabcebc9e017e4ec7e6322b378317a542c08b4cb68479c1efc716/numpy-2.5.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ab84dc6b074fa881cae55bea94cc4f68e285181ba7f32497bf7dee6b1496165b", size = 6656266, upload-time = "2026-07-04T17:07:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/fb7615b211b82a32f44d5180a6d421b61f84d4fadd578b48ba4ac34e189f/numpy-2.5.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caf3e317d33d60c37986b452613f4ab51246d0691350c03d0cb4a898627f4a95", size = 15179720, upload-time = "2026-07-04T17:07:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/0f992cb24560673496c5d68de61913b57166ce530ffda07c1f280e0cc464/numpy-2.5.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54ad769f17bc2d833b620851989f62054fb9ab93c969d9e1dc3c8e3d56beea21", size = 16664835, upload-time = "2026-07-04T17:07:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2f/97d6475ee91afe2587797d09446f9d3e475ad4cb681662d824809327b75a/numpy-2.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c12afb53450fa976d4c681c50a7423729a4c51c0465ed9f32b8a9cabbc472373", size = 16539135, upload-time = "2026-07-04T17:07:22.015Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/4db81e4ba0be7e2776b1de68c82aa862c7f8ec27e1b4927d4ae075e20678/numpy-2.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c11c405efc5ff6816d5983c96cdfa215bab3428961243af3ff59b228490438", size = 18426684, upload-time = "2026-07-04T17:07:24.941Z" }, + { url = "https://files.pythonhosted.org/packages/1f/64/c0ba2d90724d450279a7df8f32057241070250a26a7e2b5337d77347f481/numpy-2.5.1-cp314-cp314-win32.whl", hash = "sha256:f2479a47f8d5932d1718168a681ad6e536a9df484c83cfcf9de365e164537ace", size = 6116103, upload-time = "2026-07-04T17:07:27.622Z" }, + { url = "https://files.pythonhosted.org/packages/c1/1a/837f9ed7405adcd7a40538792eb169eddd8fa5630c16a1ef49dae71a30f4/numpy-2.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:24d0eb82c0541d3415a33425db64ae439dffccd7b4dbcb30e7c35120205c506a", size = 12562177, upload-time = "2026-07-04T17:07:29.887Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/49707938b6dd0a78a9178dd93227dc89e4c11af47f5c798d70366e8d0483/numpy-2.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:5a4c988b38d261deeeaad9954e3deb091ad905c94e8bb6708654ef1d97f286b0", size = 10627739, upload-time = "2026-07-04T17:07:32.568Z" }, + { url = "https://files.pythonhosted.org/packages/a6/c7/bb4b882cfe7f299cbc8b66e42e7dd78cf9d14e40f9469fc5e3db7e15b3bd/numpy-2.5.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a33276be12fa045805f477f22482088b66bb758ffbe89a9d21457de863a32e22", size = 11894709, upload-time = "2026-07-04T17:07:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/3f/5af7f4a7f6224aef48017aa82bb6174c7a659d724be0c75017b7e64a55b4/numpy-2.5.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:f089d7b00756190aacf1f5d34bdf38c3c430ac82b4f868f8cede73380460fce7", size = 5453810, upload-time = "2026-07-04T17:07:37.495Z" }, + { url = "https://files.pythonhosted.org/packages/20/c9/3474309bc94d634d3f9c3eddf03250ecb8c22cd948ef16fef69a77cc5d7b/numpy-2.5.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:09e9bfd8d2cf479c7d174804fb3811c53a8e9f20a37444008606b57d6b7a826d", size = 6761189, upload-time = "2026-07-04T17:07:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/90/8a/558ae39fdd55d7e7f7fef9a84a6e964ac6b23edbd2a07e52bb084500507d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e68d8dd1e7eba712948f2053a29ec86917bc70ba1358df869d9f06649ef9cf09", size = 15225039, upload-time = "2026-07-04T17:07:41.682Z" }, + { url = "https://files.pythonhosted.org/packages/63/27/ca7392b2d030277bdf0273e7d23255b3ee57d57a7c170a6f4fb3981e1e5d/numpy-2.5.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99d5095fa265a0c4152e7bb12759e14381ef5496152f1ce58f44bdf55c44beb4", size = 16701306, upload-time = "2026-07-04T17:07:44.611Z" }, + { url = "https://files.pythonhosted.org/packages/02/42/03d53ae7996c44d4374a8262e9dc41671fd56cbb98f7d47ef85cf5da4c6b/numpy-2.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ab87a91b3cc3382b8956095bd8f95e00cf679bb81554339be1a2ba404a1473c1", size = 16589955, upload-time = "2026-07-04T17:07:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/7b/15/6c1784ae469640e65db111e9a34b3d0f14d91e8a38b9ce34810ced370dbb/numpy-2.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:224ca51130ef7da85bea2191625181cb4f337f9cb64b471f10c1a12aa8b60077", size = 18464252, upload-time = "2026-07-04T17:07:50.684Z" }, + { url = "https://files.pythonhosted.org/packages/94/a8/f98e50356cf167df656c526c2dfeec2d7dde182f2a3da4b458a5938e2776/numpy-2.5.1-cp314-cp314t-win32.whl", hash = "sha256:6eab239876581b2b3c5a242281b6007bbdbcd1c7085d7709bb57c5929b11e6bf", size = 6263298, upload-time = "2026-07-04T17:07:53.445Z" }, + { url = "https://files.pythonhosted.org/packages/72/ac/96ae880cdecad0b3275d9359fcec72667b49a4863c9f12942e43679dda02/numpy-2.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:83ce9c80d5b521b0d77ddcbe5447c218d247929b6cc056ca5351342accfff0af", size = 12748623, upload-time = "2026-07-04T17:07:55.384Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, +] + +[[package]] +name = "opt-einsum" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004, upload-time = "2024-09-26T14:33:24.483Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "phiflow" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, + { name = "packaging" }, + { name = "phiml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/57/6990d61a59c6a7daab8e5fd318d37682f72831bcd9b116ed5dd779d1801b/phiflow-3.4.0.tar.gz", hash = "sha256:5e447df3944d418bdaa198cc3ed92904f6f453565a5fdd1553f0996030c7db29", size = 207372, upload-time = "2025-08-02T21:29:06.176Z" } + +[[package]] +name = "phiml" +version = "1.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5py" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/71/67ec7117dbc6cc922b3a8e7df743591c07a46fc5976fa0ab3f91150ca114/phiml-1.16.1.tar.gz", hash = "sha256:b4acb8bfc789ca905d94f2a5fd26a8b5e073f134da49f7091eb84ca0b0068f9a", size = 378239, upload-time = "2026-05-31T15:36:38.67Z" } + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage" }, + { name = "pluggy" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/19/ca10ead60b0acc80b2b833c2c4a4f2ff753d0f58b811f70d911c7e94a25c/scipy-1.18.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:7bd21faaf5a1a3b2eff922d02db5f191b99a6518db9078a8fb23169f6d22259a", size = 31056519, upload-time = "2026-06-19T14:59:45.203Z" }, + { url = "https://files.pythonhosted.org/packages/96/72/1e6442a00cd2924d361aa1b642ab6373ec35c6fabf311a760be9f76e0f13/scipy-1.18.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:265915e79107de9f946b855e50d7470d5893ec3f54b342e1aa6201cbdcd8bb6b", size = 28681889, upload-time = "2026-06-19T14:59:48.103Z" }, + { url = "https://files.pythonhosted.org/packages/9b/2d/11dd93d21e147a73ba22bd75c0b9208d3a2e0ec76d53170ce7d9029b1015/scipy-1.18.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9ab7b758be6940954a713ee466e2043e9f6e2ed965c1fce5c91039f4be3d90a9", size = 20423580, upload-time = "2026-06-19T14:59:50.665Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/93552f75e0d2a7dd115a45e59209c51e8d514daff02fc887d2623be06fe1/scipy-1.18.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:97b6cddaaee0a779ef6b5ca83c9604b27cc16b2b8fc22c142652df8793319fb8", size = 23054441, upload-time = "2026-06-19T14:59:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/3c/23/21f5e703643d66f21faa6b4c73195bfcad70c55efcb4f1ab327cd7c4101a/scipy-1.18.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52a96e21517c7292375c0e27dd796a811f03fcea5fd4d108fdfea8145dcf17ab", size = 33968720, upload-time = "2026-06-19T14:59:56.415Z" }, + { url = "https://files.pythonhosted.org/packages/dd/aa/1b939f6c67ed68635bb538e6752d3dacc02f66535182e939a89581a44e9c/scipy-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f55797419e16e7f30cf88ffb3113ce0467f00cfe3f70d5c281730b21769bfc2", size = 35287115, upload-time = "2026-06-19T14:59:59.411Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ff/eec46be7e9234208f801062b53e1983085eddebd693f6c9bfb03b459830d/scipy-1.18.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ad033410e2e0672ffdc1042110cef20e1c46f8fd0616cee1d44d8d58fad8fc11", size = 35577989, upload-time = "2026-06-19T15:00:02.235Z" }, + { url = "https://files.pythonhosted.org/packages/84/ca/210d4759c7210bb7d269437421959b39a33434e2776b60c5cb8a763bb30a/scipy-1.18.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a55985d54c769c872e64b7f4c8a81cc30ef700cc04296abbbf3705439c126de", size = 37421717, upload-time = "2026-06-19T15:00:05.102Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/9a9edb45345bd6744da5ddfb6628e5d5185920494c6a67ec45b6381004cb/scipy-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:71ccc8faa2dd16ac310233203474a8b5cb67f10dedd54a3116d34943f4b19132", size = 36597428, upload-time = "2026-06-19T15:00:08.112Z" }, + { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +]