diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..c1d092e
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,88 @@
+name: Code quality
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+permissions:
+ contents: read
+
+jobs:
+ lockfile:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+ - run: uv lock --locked
+
+ lint:
+ runs-on: ubuntu-latest
+ needs: lockfile
+ steps:
+ - uses: actions/checkout@v5
+ - uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+ - run: uv sync --locked --group dev
+ - run: uv run ruff check
+
+ format:
+ runs-on: ubuntu-latest
+ needs: lockfile
+ steps:
+ - uses: actions/checkout@v5
+ - uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+ - run: uv sync --locked --group dev
+ - run: uv run ruff format --check
+
+ typecheck:
+ runs-on: ubuntu-latest
+ needs: lockfile
+ steps:
+ - uses: actions/checkout@v5
+ - uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+ - run: uv sync --locked --group dev
+ - run: uv run mypy
+
+ test:
+ runs-on: ubuntu-latest
+ needs: lockfile
+ env:
+ QT_QPA_PLATFORM: offscreen
+ steps:
+ - uses: actions/checkout@v5
+ - uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+ - name: Install system deps for PyQt5
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libgl1 \
+ libegl1 \
+ libxkbcommon0 \
+ libdbus-1-3
+ - run: uv sync --locked --group dev
+ - run: uv run pytest test/ -v
+
+ build:
+ runs-on: ubuntu-latest
+ needs: [lint, format, typecheck, test]
+ steps:
+ - uses: actions/checkout@v5
+ - uses: astral-sh/setup-uv@v7
+ with:
+ enable-cache: true
+ python-version: "3.13"
+ - run: uv build
diff --git a/.github/workflows/sphinx-docs.yml b/.github/workflows/sphinx-docs.yml
index bd323f8..ee109d0 100644
--- a/.github/workflows/sphinx-docs.yml
+++ b/.github/workflows/sphinx-docs.yml
@@ -2,7 +2,11 @@ name: "Sphinx: Render docs"
on:
push:
+ branches:
+ - master
pull_request:
+ branches:
+ - master
jobs:
build:
@@ -14,20 +18,16 @@ jobs:
with:
persist-credentials: false
- - name: Set up conda
- uses: conda-incubator/setup-miniconda@v3
+ - name: Set up uv
+ uses: astral-sh/setup-uv@v3
with:
- auto-activate-base: true
- activate-environment: instrumentserver-docs
- environment-file: environment-docs.yml
-
- - name: Install instrumentserver package
- shell: bash -l {0}
- run: pip install -e .
-
+ enable-cache: true
+ - name: Install system deps
+ run: sudo apt-get update && sudo apt-get install -y pandoc
+ - name: Install dependencies
+ run: uv sync --frozen --group docs
- name: Build HTML
- shell: bash -l {0}
- run: cd docs && make html
+ run: uv run --directory docs -- make html
- name: Upload artifacts
uses: actions/upload-artifact@v4
@@ -40,4 +40,4 @@ jobs:
if: github.ref == 'refs/heads/master'
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- publish_dir: docs/build/
\ No newline at end of file
+ publish_dir: docs/build/
diff --git a/environment-docs.yml b/environment-docs.yml
deleted file mode 100644
index ec293e2..0000000
--- a/environment-docs.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-name: instrumentserver-docs
-channels:
- - conda-forge
- - defaults
-dependencies:
- - python=3.10
- - sphinx>=5.0
- - sphinx-autobuild>=2021.3.20
- - pydata-sphinx-theme>=0.13.0
- - myst-parser>=0.18.0
- - linkify-it-py
- - sphinx-autodoc-typehints>=1.19.0
- - nbsphinx>=0.8.0
- - nbconvert>=7.0.0
- - jupyter>=1.0.0
- - ipython>=7.0.0
- - scipy>=1.0
- - pandas>=1.0
- - xarray>=0.16
- - packaging>=20.0
- - pip
\ No newline at end of file
diff --git a/instrumentserver/client/__init__.py b/instrumentserver/client/__init__.py
deleted file mode 100644
index 6213417..0000000
--- a/instrumentserver/client/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from .core import sendRequest
-from .proxy import ProxyInstrument, Client, QtClient, SubClient, ClientStation
-
diff --git a/instrumentserver/gui/__init__.py b/instrumentserver/gui/__init__.py
deleted file mode 100644
index c9406ef..0000000
--- a/instrumentserver/gui/__init__.py
+++ /dev/null
@@ -1,48 +0,0 @@
-from .. import QtCore, QtWidgets, resource
-
-
-def getStyleSheet():
- f = QtCore.QFile(":/style.css")
- if f.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text):
- style = f.readAll()
- f.close()
- return str(style, 'utf-8')
-
-
-def widgetDialog(w: QtWidgets.QWidget):
- dg = QtWidgets.QDialog()
- dg.setWindowTitle('instrumentserver')
- dg.setWindowFlag(QtCore.Qt.WindowMinimizeButtonHint)
- dg.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint)
- dg.widget = w # type: ignore[attr-defined] # I am pretty sure the stubs are wrong for this one.
-
- css = getStyleSheet()
- w.setStyleSheet(css)
-
- lay = QtWidgets.QVBoxLayout(dg)
- lay.addWidget(w)
- lay.setContentsMargins(0, 0, 0, 0)
- dg.setLayout(lay)
-
- dg.show()
- return dg
-
-
-def widgetMainWindow(w: QtWidgets.QWidget, name: str = 'instrumentserver'):
- mw = QtWidgets.QMainWindow()
- mw.setWindowTitle(name)
- mw.setCentralWidget(w)
-
- css = getStyleSheet()
- w.setStyleSheet(css)
-
- mw.show()
- return mw
-
-
-def keepSmallHorizontally(w: QtWidgets.QWidget):
- w.setSizePolicy(
- QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed,
- QtWidgets.QSizePolicy.Minimum)
- )
-
diff --git a/instrumentserver/testing/dummy_instruments/generic.py b/instrumentserver/testing/dummy_instruments/generic.py
deleted file mode 100644
index 1a64398..0000000
--- a/instrumentserver/testing/dummy_instruments/generic.py
+++ /dev/null
@@ -1,226 +0,0 @@
-# mypy: ignore-errors
-# No need to mypy check dummy testing instruments.
-
-from typing import List
-
-from qcodes import Instrument
-from qcodes.utils import validators
-from qcodes.math_utils.field_vector import FieldVector
-import numpy as np
-import time
-
-
-class DummyChannel(Instrument):
- def __init__(self, name: str, *args, **kwargs):
- super().__init__(name, *args, **kwargs)
- self.add_parameter('ch0',
- set_cmd=None,
- vals=validators.Numbers(0, 1),
- initial_value=0)
-
- self.add_parameter('ch1', unit='v',
- set_cmd=None,
- vals=validators.Numbers(-1, 1),
- initial_value=1)
-
- self.functions['dummy_function'] = self.dummy_function
-
- def dummy_function(self, *args, **kwargs):
- """Dummy function for specific channels used for testing"""
- print(f'the dummy chanel: {self.name} has been activated with:')
- print(f'args: {args}')
- print(f'kwargs: {kwargs}')
- return True
-
-
-class DummyInstrumentWithSubmodule(Instrument):
- """A dummy instrument with submodules."""
-
- def __init__(self, name: str, address=None, first_arg=None, second_arg=None, *args, **kwargs):
- super().__init__(name, *args, **kwargs)
- self.address = address
-
- self.first_arg = first_arg
- self.second_arg = second_arg
-
- self.add_parameter('param0',
- set_cmd=None,
- vals=validators.Numbers(0, 1),
- initial_value=0)
-
- self.add_parameter('param1', unit='v',
- set_cmd=None,
- vals=validators.Numbers(-1, 1),
- initial_value=1)
-
- self.add_parameter('int_param1', unit='v',
- set_cmd=None,
- vals=validators.Ints(-200, 200),
- initial_value=1)
-
- for chan_name in ('A', 'B', 'C'):
- channel = DummyChannel('Chan{}'.format(chan_name))
- self.add_submodule(chan_name, channel)
-
- self.functions['test_func'] = self.test_func
- self.functions['dummy_function'] = self.dummy_function
-
- def test_func(self, a, b, *args, c: List[int] = [10, 11], **kwargs):
- """
- This is a test function, of course you knew this from the tittle but It's nice to have documentation, isn't it?
-
- :param a: Very nice parameter.
- :param b: Even nicer parameter
- :param c: This one sucks though.
- """
- return a, b, args[0], c, kwargs['d'], self.param0()
-
- def dummy_function(self, *args, **kwargs):
- """
- Such a dumb dummy function here doing nothing other than printing and occupying your precious, precious terminal
- space.
- """
- print(f'the dummy chanel: {self.name} has been activated with:')
- print(f'args: {args}')
- print(f'kwargs: {kwargs}')
- return self.address, self.first_arg, self.second_arg
-
-
-class DummyInstrumentTimeout(Instrument):
- """A dummy instrument to test timeout situations."""
- def __init__(self, name: str, *args, **kwargs):
- super().__init__(name, *args, **kwargs)
-
- self.random = np.random.randint(10000)
- self._param1 = 1
- self._param2 = 2
- self._p1_get_counter = 0
-
- self.add_parameter('random_int', get_cmd=self.get_random)
- self.add_parameter('param1', get_cmd= self._get_param1, set_cmd=lambda p: setattr(self, '_param1', p))
- self.add_parameter('param2', get_cmd=lambda : self._param2, set_cmd=lambda p: setattr(self, '_param2', p))
-
- def _get_param1(self):
- # for testing potentially redundant/duplicate get calls
- print(f"-------------- getting {self.name}.param1, count {self._p1_get_counter}----------------")
- self._p1_get_counter += 1
- return self._param1
-
- def get_random(self):
- return self.random
-
- def get_random_timeout(self, wait_time=10):
- time.sleep(wait_time)
- return self.get_random()
-
-
-
-class DummyInstrumentRandomNumber(Instrument):
- """A dummy instrument with a few parameters that have random numbers generated on demand"""
-
- def __init__(self, name: str, *args, **kwargs):
- super().__init__(name, *args, **kwargs)
-
- self.add_parameter('param0',
- set_cmd=None,
- vals=validators.Numbers(1, 10),
- initial_value=1)
-
- self.add_parameter('param1',
- set_cmd=None,
- vals=validators.Numbers(10, 20),
- initial_value=10)
-
- self.add_parameter('param2',
- set_cmd=None,
- vals=validators.Numbers(20, 30),
- initial_value=20)
-
- self.add_parameter('param3',
- set_cmd=None,
- vals=validators.Numbers(30, 40),
- initial_value=30)
-
- self.add_parameter('param4',
- set_cmd=None,
- vals=validators.Numbers(40, 50),
- initial_value=40)
-
- def generate_data(self, name: str):
-
- if name == 'param0':
- self.parameters[name].set(np.random.randint(1, 10))
- if name == 'param1':
- self.parameters[name].set(np.random.randint(10, 20))
- if name == 'param2':
- self.parameters[name].set(np.random.randint(20, 30))
- if name == 'param3':
- self.parameters[name].set(np.random.randint(30, 40))
- if name == 'param4':
- self.parameters[name].set(np.random.randint(40, 50))
-
- def get(self, param_name):
- self.generate_data(param_name)
- return self.parameters[param_name].get()
-
-
-class FieldVectorIns(Instrument):
- """
- class used to develop json serialization and guis
- """
- def __init__(self, name, starting_parameter=22, *args, **kwargs):
- super().__init__(name=name, *args, **kwargs)
-
- self.field_vector = FieldVector(x=1, y=1, z=1)
- self.complex_value = 1 + 1j
- self.complex_lst = [1 + 1j, -2 - 2j]
- self.starting_parameter = starting_parameter
-
- self.add_parameter(name="field",
- label='target field',
- unit='T',
- get_cmd=self.get_field,
- set_cmd=self.set_field,
- )
-
- self.add_parameter(name='complex',
- label='complex value',
- unit='',
- get_cmd=self.get_complex,
- set_cmd=self.set_complex,
- )
-
- self.add_parameter(name='complex_list',
- label='complex list',
- unit='',
- get_cmd=self.get_complex_list,
- set_cmd=self.set_complex_list,
- )
-
- def get_starting_parameter(self):
- return self.starting_parameter
-
- def set_starting_parameter(self, new_value):
- pass
-
- def get_field(self):
- return self.field_vector
-
- def set_field(self, field_vector: FieldVector):
- self.field_vector = field_vector
-
- def get_complex(self):
- return self.complex_value
-
- def set_complex(self, value: complex):
- self.complex_value = value
-
- def get_complex_list(self):
- return self.complex_lst
-
- def set_complex_list(self, value):
- self.complex_lst = value
-
- def generic_function(self):
- print(f'this generic function has been called')
- return 3
diff --git a/instrumentserver/testing/dummy_instruments/rf.py b/instrumentserver/testing/dummy_instruments/rf.py
deleted file mode 100644
index 4fc9d2a..0000000
--- a/instrumentserver/testing/dummy_instruments/rf.py
+++ /dev/null
@@ -1,162 +0,0 @@
-import numpy as np
-from scipy import constants # type: ignore[import-untyped] # We don't need mypy checks for this dependency that is only used here.
-
-from qcodes import Instrument, ParameterWithSetpoints, find_or_create_instrument
-from qcodes.utils import validators
-
-
-
-class ResonatorResponse(Instrument):
- """A dummy instrument that generates the response of a resonator measured in
- reflection.
-
- Behavior is essentially that of a VNA, with the resonator and system
- properties added as parameters.
- """
-
- def __init__(self, name, f0=5e9, df=1e6, **kw):
- super().__init__(name, **kw)
-
- self._frq_mod = 0.0
- self._frq_mod_multiply = False
-
- # add params of the resonator and the virtual detection chain
- self.add_parameter('resonator_frequency', set_cmd=None, unit='Hz',
- vals=validators.Numbers(1, 50e9),
- initial_value=f0)
- self.add_parameter('resonator_linewidth', set_cmd=None, unit='Hz',
- vals=validators.Numbers(1, 1e9),
- initial_value=df)
- self.add_parameter('noise_temperature', set_cmd=None, unit='K',
- vals=validators.Numbers(0.05, 3000),
- initial_value=4.0)
- self.add_parameter('input_attenuation', set_cmd=None, unit='dB',
- vals=validators.Numbers(0, 200),
- initial_value=70)
-
- # actual instrument parameters
- self.add_parameter('start_frequency', set_cmd=None, unit='Hz',
- vals=validators.Numbers(20e3, 19.999e9),
- initial_value=20e3)
- self.add_parameter('stop_frequency', set_cmd=None, unit='Hz',
- vals=validators.Numbers(20.1e3, 20e9),
- initial_value=20e9)
- self.add_parameter('npoints', set_cmd=None, vals=validators.Ints(2, 40001),
- initial_value=1601)
- self.add_parameter('bandwidth', set_cmd=None, unit='Hz',
- vals=validators.Numbers(1, 1e6), initial_value=10e3)
- self.add_parameter('power', set_cmd=None, unit='dBm',
- vals=validators.Numbers(-100, 0), initial_value=-100)
-
- # data parameters
- self.add_parameter('frequency', unit='Hz',
- vals=validators.Arrays(shape=(self.npoints.get_latest,)),
- get_cmd=self._frequency_vals,
- snapshot_value=False, )
- self.add_parameter('data',
- parameter_class=ParameterWithSetpoints,
- setpoints=[self.frequency, ],
- vals=validators.Arrays(
- shape=(self.npoints.get_latest,),
- valid_types=[np.complexfloating],
- ),
- get_cmd=self._get_data, )
-
- def modulate_frequency(self, delta: float = 0, multiply=False) -> None:
- """Add an offset to the resonance frequency.
-
- If `multiply` is ``True``, the change in frequency is the product of `delta`
- and the set frequency. If ``False``, then `delta` is added.
- """
- self._frq_mod = delta
- self._frq_mod_multiply = multiply
-
- # private utility methods
- def _frequency_vals(self):
- return np.linspace(self.start_frequency(), self.stop_frequency(), self.npoints())
-
- def _get_data(self):
- f0 = self.resonator_frequency()
- if self._frq_mod_multiply:
- f0 *= self._frq_mod
- else:
- f0 += self._frq_mod
-
- fvals = self._frequency_vals()
- data = self._resonator_reflection_signal(
- fvals,
- f0,
- self.resonator_linewidth(),
- self.power() - self.input_attenuation(),
- self.bandwidth(),
- self.noise_temperature())
-
- return data
-
- def _resonator_reflection_signal(self, fvals, f0, df, P_in, BW, T_N):
- """Compute a realistic resonator reflection signal of a one-port
- resonator, including random noise.
-
- :param fvals: probe frequencies [Hz]
- :param f0: resonance frequency [Hz]
- :param df: line width [Hz]
- :param P_in: incident power [dBm]
- :param BW: detection bandwidth [Hz]
- :param T_N: noise temperature [K]
-
- :returns: dummy data, same shape as `fvals`.
- """
- det = fvals - f0
- pwr = 1e-3 * 10 ** (P_in / 10) # convert dBm to Watt
- ideal_signal = (2j * det - df) / (2j * det + df)
- noise = (constants.k * T_N * BW / pwr) ** .5
- noise_real = np.random.normal(size=ideal_signal.size, loc=0, scale=noise)
- noise_imag = np.random.normal(size=ideal_signal.size, loc=0, scale=noise)
- return ideal_signal + noise_real + 1j * noise_imag
-
-
-class Generator(Instrument):
- """A simple dummy that mocks an RF generator."""
-
- def __init__(self, name, *arg, **kw):
- super().__init__(name, *arg, **kw)
-
- self.add_parameter('frequency', unit='Hz',
- set_cmd=None,
- vals=validators.Numbers(1e3, 20e9),
- initial_value=10e9)
-
- self.add_parameter('power', unit='dBm',
- set_cmd=None,
- vals=validators.Numbers(-100, 25),
- initial_value=-100)
-
- self.add_parameter('rf_on', set_cmd=None,
- vals=validators.Bool(),
- initial_value=False)
-
-
-class FluxControl(Instrument):
- """A dummy that hooks to :class:`.ResonatorResponse` and modifies its
- resonance frequency as if the resonator were a squid."""
-
- def __init__(self, name: str, resonator_instrument: str,
- *args, **kwargs):
- super().__init__(name, *args, **kwargs)
-
- self._resonator = find_or_create_instrument(instrument_class=ResonatorResponse,
- name=resonator_instrument)
-
- self.add_parameter('inductive_participation_ratio',
- set_cmd=None,
- vals=validators.Numbers(0, 1),
- initial_value=0.05)
-
- self.add_parameter('flux', unit='Phi_0',
- set_cmd=self._set_flux,
- vals=validators.Numbers(-1, 1),
- initial_value=0)
-
- def _set_flux(self, flux):
- mod = 1./(1. + self.inductive_participation_ratio() / np.abs(np.cos(np.pi*flux)))
- self._resonator.modulate_frequency(mod, True)
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..c43b5f6
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,114 @@
+[build-system]
+requires = ["setuptools>=68", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "instrumentserver"
+version = "0.0.1"
+description = "Distributed instrument control server and client for QCoDeS."
+readme = "README.md"
+license = {file = "LICENSE"}
+requires-python = ">=3.11"
+authors = [
+ {name = "Wolfgang Pfaff", email = "wolfgangpfff@gmail.com"},
+ {name = "Marcos Frenkel", email = "marcosf2@illinois.edu"}
+]
+dependencies = [
+ "pyqt5",
+ "pyzmq",
+ "qcodes",
+ "qtpy",
+ "scipy",
+ "numpy",
+ "pandas",
+ "jsonschema",
+ "ruamel.yaml",
+ "PyYAML",
+]
+
+[project.optional-dependencies]
+monitoring = ["influxdb-client"]
+
+[project.urls]
+Homepage = "https://github.com/toolsforexperiments/instrumentserver"
+
+[project.scripts]
+instrumentserver = "instrumentserver.apps:serverScript"
+instrumentserver-client-station = "instrumentserver.apps:clientStationScript"
+instrumentserver-detached = "instrumentserver.apps:detachedServerScript"
+instrumentserver-listener = "instrumentserver.monitoring.listener:startListener"
+instrumentserver-param-manager = "instrumentserver.apps:parameterManagerScript"
+
+[tool.setuptools]
+package-dir = {"" = "src"}
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+[tool.ruff]
+exclude = ["docs"]
+
+[tool.ruff.lint]
+extend-select = ["I"]
+
+[tool.ruff.lint.per-file-ignores]
+"*.ipynb" = ["E402"]
+
+[tool.mypy]
+files = ["src"]
+exclude = ["src/instrumentserver/resource\\.py$"]
+strict_optional = true
+show_column_numbers = true
+warn_unused_ignores = true
+warn_unused_configs = true
+warn_redundant_casts = true
+no_implicit_optional = true
+disallow_untyped_defs = true
+show_error_codes = true
+enable_error_code = "ignore-without-code"
+
+[[tool.mypy.overrides]]
+module = [
+ "PyQt5",
+ "PyQt5.*",
+ "qcodes",
+ "qcodes.*",
+ "qtpy",
+ "qtpy.*",
+ "scipy",
+ "scipy.*",
+ "zmq",
+ "zmq.*",
+ "ruamel",
+ "ruamel.*",
+ "jsonschema",
+ "jsonschema.*",
+ "yaml",
+ "pandas",
+ "pandas.*",
+]
+ignore_missing_imports = true
+
+[tool.pytest.ini_options]
+testpaths = ["test/pytest"]
+qt_api = "pyqt5"
+log_cli = true
+
+[tool.coverage.run]
+branch = true
+
+[dependency-groups]
+dev = [
+ "pytest>=9.0.2",
+ "pytest-qt>=4.5.0",
+ "pytest-cov",
+ "ruff",
+ "mypy",
+]
+docs = [
+ "sphinx",
+ "pydata-sphinx-theme",
+ "myst-parser",
+ "nbsphinx",
+ "linkify-it-py",
+]
diff --git a/setup.py b/setup.py
deleted file mode 100644
index b32c990..0000000
--- a/setup.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from setuptools import setup
-
-setup(name='instrumentserver',
- version='0.0.1',
- description='TBD',
- url='https://github.com/toolsforexperiments/instrumentserver',
- author='Wolfgang Pfaff',
- author_email='wolfgangpfff@gmail.com',
- license='MIT',
- packages=['instrumentserver'],
- zip_safe=False,
- entry_points={
- "console_scripts": [
- "instrumentserver = instrumentserver.apps:serverScript",
- "instrumentserver-detached = instrumentserver.apps:detachedServerScript",
- "instrumentserver-client-station = instrumentserver.apps:clientStationScript",
- "instrumentserver-param-manager = instrumentserver.apps:parameterManagerScript",
- "instrumentserver-listener = instrumentserver.monitoring.listener:startListener",
- ]},
- install_requires = [
- 'pyzmq',
- 'qcodes',
- 'qtpy',
- 'pyqt5',
- 'bokeh',
- 'scipy'
- ]
- )
diff --git a/instrumentserver/__init__.py b/src/instrumentserver/__init__.py
similarity index 57%
rename from instrumentserver/__init__.py
rename to src/instrumentserver/__init__.py
index 482657e..c8b9129 100644
--- a/instrumentserver/__init__.py
+++ b/src/instrumentserver/__init__.py
@@ -1,9 +1,9 @@
-import sys
-import os
-import logging
import json
+import os
-from qtpy import QtGui, QtCore, QtWidgets
+from qtpy import QtCore as QtCore
+from qtpy import QtGui as QtGui
+from qtpy import QtWidgets as QtWidgets
def getInstrumentserverPath(*subfolder: str) -> str:
@@ -20,15 +20,15 @@ def getInstrumentserverPath(*subfolder: str) -> str:
return os.path.join(path, *subfolder)
-PARAMS_SCHEMA_PATH = os.path.join(getInstrumentserverPath('schemas'),
- 'parameters.json')
+PARAMS_SCHEMA_PATH = os.path.join(getInstrumentserverPath("schemas"), "parameters.json")
DEFAULT_PORT = 5555
with open(PARAMS_SCHEMA_PATH) as f:
paramDictSchema = json.load(f)
-from .log import setupLogging, logger
+from .client import Client # noqa: E402
+from .log import logger as logger # noqa: E402
+from .log import setupLogging as setupLogging # noqa: E402
-from .client import Client
-InstrumentClient = Client
\ No newline at end of file
+InstrumentClient = Client
diff --git a/instrumentserver/apps.py b/src/instrumentserver/apps.py
similarity index 56%
rename from instrumentserver/apps.py
rename to src/instrumentserver/apps.py
index 8610a73..0d881d1 100644
--- a/instrumentserver/apps.py
+++ b/src/instrumentserver/apps.py
@@ -1,31 +1,29 @@
-import os
import argparse
import logging
+import os
import signal
from pathlib import Path
+from typing import Any, Optional
-from . import QtWidgets, QtCore
-from .log import setupLogging
-from .config import loadConfig
-from .server.application import startServerGuiApplication
-from .server.core import startServer
+from instrumentserver.server.application import DetachedServerGui
+from . import QtCore, QtWidgets
from .client import Client, ClientStation
from .client.application import ClientStationGui
+from .config import loadConfig
from .gui import widgetMainWindow
from .gui.instruments import ParameterManagerGui
+from .log import setupLogging
+from .server.application import startServerGuiApplication
+from .server.core import startServer
from .server.pollingWorker import PollingWorker
-from instrumentserver.server.application import DetachedServerGui
-
-
-setupLogging(addStreamHandler=True,
- logFile=os.path.abspath('instrumentserver.log'))
-logger = logging.getLogger('instrumentserver')
+setupLogging(addStreamHandler=True, logFile=os.path.abspath("instrumentserver.log"))
+logger = logging.getLogger("instrumentserver")
logger.setLevel(logging.INFO)
-def server(**kwargs):
+def server(**kwargs: Any) -> int:
app = QtCore.QCoreApplication([])
# this allows us to kill the server by KeyboardInterrupt
@@ -36,31 +34,45 @@ def server(**kwargs):
return app.exec_()
-def serverWithGui(**kwargs):
+def serverWithGui(**kwargs: Any) -> int:
app = QtWidgets.QApplication([])
- window = startServerGuiApplication(**kwargs)
+ startServerGuiApplication(**kwargs)
return app.exec_()
def serverScript() -> None:
- parser = argparse.ArgumentParser(description='Starting the instrumentserver')
+ parser = argparse.ArgumentParser(description="Starting the instrumentserver")
parser.add_argument("-p", "--port", default=5555)
parser.add_argument("--gui", default=True)
parser.add_argument("--allow_user_shutdown", default=False)
- parser.add_argument("-a", "--listen_at", type=str, nargs="*",
- help="On which network addresses we listen.")
- parser.add_argument("-i", "--init_script", default='',
- type=str)
- parser.add_argument("-c", "--config", type=str, default='')
+ parser.add_argument(
+ "-a",
+ "--listen_at",
+ type=str,
+ nargs="*",
+ help="On which network addresses we listen.",
+ )
+ parser.add_argument("-i", "--init_script", default="", type=str)
+ parser.add_argument("-c", "--config", type=str, default="")
args = parser.parse_args()
# Load and process the config file if any.
configPath = args.config
- stationConfig, serverConfig, guiConfig, tempFile, pollingRates, pollingThread, ipAddresses = None, None, None, None, None, None, None
- if configPath != '':
+ (
+ stationConfig,
+ serverConfig,
+ guiConfig,
+ tempFile,
+ pollingRates,
+ pollingThread,
+ ipAddresses,
+ ) = None, None, None, None, None, None, None
+ if configPath != "":
# Separates the corresponding settings into the 5 necessary parts
- stationConfig, serverConfig, guiConfig, tempFile, pollingRates, ipAddresses = loadConfig(configPath)
+ stationConfig, serverConfig, guiConfig, tempFile, pollingRates, ipAddresses = (
+ loadConfig(configPath)
+ )
if pollingRates is not None and pollingRates != {}:
pollingThread = QtCore.QThread()
pollWorker = PollingWorker(pollingRates=pollingRates)
@@ -68,25 +80,29 @@ def serverScript() -> None:
pollingThread.started.connect(pollWorker.run)
pollingThread.start()
- if args.gui == 'False':
- server(port=args.port,
- allowUserShutdown=args.allow_user_shutdown,
- addresses=args.listen_at,
- initScript=args.init_script,
- serverConfig=serverConfig,
- stationConfig=stationConfig,
- guiConfig=guiConfig,
- pollingThread=pollingThread,
- ipAddresses=ipAddresses)
+ if args.gui == "False":
+ server(
+ port=args.port,
+ allowUserShutdown=args.allow_user_shutdown,
+ addresses=args.listen_at,
+ initScript=args.init_script,
+ serverConfig=serverConfig,
+ stationConfig=stationConfig,
+ guiConfig=guiConfig,
+ pollingThread=pollingThread,
+ ipAddresses=ipAddresses,
+ )
else:
- serverWithGui(port=args.port,
- addresses=args.listen_at,
- initScript=args.init_script,
- serverConfig=serverConfig,
- stationConfig=stationConfig,
- guiConfig=guiConfig,
- pollingThread=pollingThread,
- ipAddresses=ipAddresses)
+ serverWithGui(
+ port=args.port,
+ addresses=args.listen_at,
+ initScript=args.init_script,
+ serverConfig=serverConfig,
+ stationConfig=stationConfig,
+ guiConfig=guiConfig,
+ pollingThread=pollingThread,
+ ipAddresses=ipAddresses,
+ )
# Close and delete the temporary files
if tempFile is not None:
@@ -96,7 +112,9 @@ def serverScript() -> None:
def parameterManagerScript() -> None:
- parser = argparse.ArgumentParser(description='Starting a parameter manager instrument GUI')
+ parser = argparse.ArgumentParser(
+ description="Starting a parameter manager instrument GUI"
+ )
parser.add_argument("--name", default="parameter_manager")
parser.add_argument("--port", default=5555)
args = parser.parse_args()
@@ -110,17 +128,20 @@ def parameterManagerScript() -> None:
pm = cli.get_instrument(args.name)
else:
pm = cli.find_or_create_instrument(
- args.name, 'instrumentserver.params.ParameterManager')
+ args.name, "instrumentserver.params.ParameterManager"
+ )
pm.fromFile()
pm.update()
- _ = widgetMainWindow(ParameterManagerGui(pm), 'Parameter Manager')
+ _ = widgetMainWindow(ParameterManagerGui(pm), "Parameter Manager")
app.exec_()
def detachedServerScript() -> None:
- parser = argparse.ArgumentParser(description='Starting a detached instance of the GUI for the server')
+ parser = argparse.ArgumentParser(
+ description="Starting a detached instance of the GUI for the server"
+ )
parser.add_argument("--host", default="localhost")
parser.add_argument("--port", default=5555)
args = parser.parse_args()
@@ -132,18 +153,22 @@ def detachedServerScript() -> None:
def clientStationScript() -> None:
- parser = argparse.ArgumentParser(description='Starting a client station GUI')
+ parser = argparse.ArgumentParser(description="Starting a client station GUI")
parser.add_argument("--host", default="localhost", help="Server host address")
parser.add_argument("--port", default=5555, type=int, help="Server port")
- parser.add_argument("-c", "--config", type=str, default='', help="Path to client station config file (YAML)")
+ parser.add_argument(
+ "-c",
+ "--config",
+ type=str,
+ default="",
+ help="Path to client station config file (YAML)",
+ )
args = parser.parse_args()
app = QtWidgets.QApplication([])
- config_path = args.config if args.config else None
+ config_path: Optional[str] = args.config if args.config else None
station = ClientStation(host=args.host, port=args.port, config_path=config_path)
window = ClientStationGui(station)
window.show()
app.exec_()
-
-
diff --git a/instrumentserver/base.py b/src/instrumentserver/base.py
similarity index 65%
rename from instrumentserver/base.py
rename to src/instrumentserver/base.py
index 5b489b0..b5623ef 100644
--- a/instrumentserver/base.py
+++ b/src/instrumentserver/base.py
@@ -1,61 +1,64 @@
-import zmq
import json
import logging
+from typing import Any, Tuple, Union
+
+import zmq
-from .blueprints import to_dict, deserialize_obj
+from .blueprints import deserialize_obj, to_dict
logger = logging.getLogger(__name__)
-def encode(data):
+
+def encode(data: Any) -> str:
return json.dumps(to_dict(data))
-def decode(data):
+def decode(data: Union[str, bytes]) -> Any:
return deserialize_obj(json.loads(data))
-def send(socket, data, use_string=True):
+def send(socket: "zmq.Socket", data: Any, use_string: bool = True) -> Any:
payload = encode(data)
if use_string:
return socket.send_string(payload)
else:
- return socket.send(payload.encode('utf-8'))
+ return socket.send(payload.encode("utf-8"))
-def recv(socket):
+def recv(socket: "zmq.Socket") -> Any:
# Try multipart receive first (ROUTER replies)
parts = socket.recv_multipart()
while socket.getsockopt(zmq.RCVMORE):
leftover = socket.recv()
- logger.warning(f"Additional part found in recv: {leftover}")
+ logger.warning(f"Additional part found in recv: {leftover!r}")
if len(parts) == 1:
data = parts[0]
- elif len(parts) == 2 and parts[0] == b'': # optional empty delimiter
+ elif len(parts) == 2 and parts[0] == b"": # optional empty delimiter
data = parts[1]
else:
data = parts[-1] # assume last part is the actual message
return decode(data)
-def send_router(socket, identity, message):
+def send_router(socket: "zmq.Socket", identity: bytes, message: Any) -> None:
socket.setsockopt(zmq.SNDTIMEO, 5000)
socket.setsockopt(zmq.LINGER, 0)
- payload = encode(message).encode('utf-8')
- socket.send_multipart([identity, b'', payload])
+ payload = encode(message).encode("utf-8")
+ socket.send_multipart([identity, b"", payload])
-def recv_router(socket):
+def recv_router(socket: "zmq.Socket") -> Tuple[bytes, Any]:
parts = socket.recv_multipart()
if len(parts) == 2:
identity, payload = parts
- elif len(parts) == 3 and parts[1] == b'':
+ elif len(parts) == 3 and parts[1] == b"":
identity, payload = parts[0], parts[2]
else:
raise ValueError(f"Malformed ROUTER message: {parts}")
return identity, decode(payload)
-def sendBroadcast(socket, name, message):
+def sendBroadcast(socket: "zmq.Socket", name: str, message: Any) -> None:
"""
broadcasts the message. It will send 2 messages: First the name with the send more flag,
followed by the message.
@@ -65,10 +68,10 @@ def sendBroadcast(socket, name, message):
:param messages: The data to send.
"""
socket.send_string(name, flags=zmq.SNDMORE)
- socket.send(encode(message).encode('utf-8'))
+ socket.send(encode(message).encode("utf-8"))
-def recvMultipart(socket):
+def recvMultipart(socket: "zmq.Socket") -> Tuple[str, Any]:
"""
Recieves the broadcast from a broadcast message. It should consist of 2 parts:
The first item is the name of the object sending it. Second part the message
diff --git a/instrumentserver/blueprints.py b/src/instrumentserver/blueprints.py
similarity index 75%
rename from instrumentserver/blueprints.py
rename to src/instrumentserver/blueprints.py
index e8524dd..94fb288 100644
--- a/instrumentserver/blueprints.py
+++ b/src/instrumentserver/blueprints.py
@@ -53,29 +53,28 @@
import inspect
import json
import logging
-from enum import Enum, unique
from collections.abc import Iterable
-from dataclasses import dataclass, field, fields, asdict, is_dataclass, Field
-from typing import Union, Optional, List, Dict, Callable, Tuple, Any, get_args, cast
+from dataclasses import asdict, dataclass, field, fields
+from enum import Enum, unique
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast, get_args
import numpy as np
from qcodes import (
- Station, Instrument, InstrumentChannel, Parameter, ParameterWithSetpoints)
+ Instrument,
+ InstrumentChannel,
+ Parameter,
+ ParameterWithSetpoints,
+)
from qcodes.instrument.base import InstrumentBase
-from qcodes.utils.validators import Validator
from .helpers import objectClassPath, typeClassPath
logger = logging.getLogger(__name__)
-INSTRUMENT_MODULE_BASE_CLASSES = [
- Instrument, InstrumentChannel, InstrumentBase
-]
+INSTRUMENT_MODULE_BASE_CLASSES = [Instrument, InstrumentChannel, InstrumentBase]
InstrumentModuleType = Union[Instrument, InstrumentChannel, InstrumentBase]
-PARAMETER_BASE_CLASSES = [
- Parameter, ParameterWithSetpoints
-]
+PARAMETER_BASE_CLASSES = [Parameter, ParameterWithSetpoints]
ParameterType = Union[Parameter, ParameterWithSetpoints]
@@ -83,16 +82,17 @@
@dataclass
class ParameterBluePrint:
"""Spec necessary for creating parameter proxies."""
+
name: str
path: str
base_class: str
parameter_class: str
gettable: bool = True
settable: bool = True
- unit: str = ''
- docstring: str = ''
+ unit: str = ""
+ docstring: str = ""
setpoints: Optional[List[str]] = None
- _class_type: str = 'ParameterBluePrint'
+ _class_type: str = "ParameterBluePrint"
def __repr__(self) -> str:
return str(self)
@@ -100,8 +100,8 @@ def __repr__(self) -> str:
def __str__(self) -> str:
return f"{self.name}: {self.parameter_class}"
- def tostr(self, indent=0):
- i = indent * ' '
+ def tostr(self, indent: int = 0) -> str:
+ i = indent * " "
ret = f"""{self.name}: {self.parameter_class}
{i}- unit: {self.unit}
{i}- path: {self.path}
@@ -112,20 +112,23 @@ def tostr(self, indent=0):
"""
return ret
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
return bluePrintToDict(self)
-def bluePrintFromParameter(path: str, param: ParameterType) -> \
- Union[ParameterBluePrint, None]:
+def bluePrintFromParameter(
+ path: str, param: ParameterType
+) -> Union[ParameterBluePrint, None]:
base_class = None
for bc in PARAMETER_BASE_CLASSES:
if isinstance(param, bc):
base_class = bc
break
if base_class is None:
- logger.warning(f"Blueprints for parameter base type of {param} are "
- f"currently not supported.")
+ logger.warning(
+ f"Blueprints for parameter base type of {param} are "
+ f"currently not supported."
+ )
return None
bp = ParameterBluePrint(
@@ -133,12 +136,12 @@ def bluePrintFromParameter(path: str, param: ParameterType) -> \
path=path,
base_class=typeClassPath(base_class),
parameter_class=objectClassPath(param),
- gettable=True if hasattr(param, 'get') else False,
- settable=True if hasattr(param, 'set') else False,
+ gettable=True if hasattr(param, "get") else False,
+ settable=True if hasattr(param, "set") else False,
unit=param.unit,
docstring=param.__doc__ or "",
)
- if hasattr(param, 'setpoints'):
+ if hasattr(param, "setpoints"):
bp.setpoints = [setpoint.name for setpoint in param.setpoints]
return bp
@@ -147,21 +150,22 @@ def bluePrintFromParameter(path: str, param: ParameterType) -> \
@dataclass
class MethodBluePrint:
"""Spec necessary for creating method proxies"""
+
name: str
path: str
call_signature_str: str
signature_parameters: dict
docstring: str = ""
- _class_type: str = 'MethodBluePrint'
+ _class_type: str = "MethodBluePrint"
- def __repr__(self):
+ def __repr__(self) -> str:
return str(self)
- def __str__(self):
+ def __str__(self) -> str:
return f"{self.name}{str(self.call_signature_str)}"
- def tostr(self, indent=0):
- i = indent * ' '
+ def tostr(self, indent: int = 0) -> str:
+ i = indent * " "
ret = f"""{self.name}{str(self.call_signature_str)}
{i}- path: {self.path}
"""
@@ -169,14 +173,16 @@ def tostr(self, indent=0):
# we might want to be careful to keep them in the correct order
@classmethod
- def signature_str_and_params_from_obj(cls, sig: inspect.Signature) -> Tuple[str, dict]:
+ def signature_str_and_params_from_obj(
+ cls, sig: inspect.Signature
+ ) -> Tuple[str, dict]:
call_signature_str = str(sig)
param_dict = {}
for name, param in sig.parameters.items():
param_dict[name] = str(param.kind)
return call_signature_str, param_dict
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
return bluePrintToDict(self)
@@ -184,7 +190,7 @@ def bluePrintFromMethod(path: str, method: Callable) -> Union[MethodBluePrint, N
sig = inspect.signature(method)
sig_str, param_dict = MethodBluePrint.signature_str_and_params_from_obj(sig)
bp = MethodBluePrint(
- name=path.split('.')[-1],
+ name=path.split(".")[-1],
path=path,
call_signature_str=sig_str,
signature_parameters=param_dict,
@@ -196,25 +202,31 @@ def bluePrintFromMethod(path: str, method: Callable) -> Union[MethodBluePrint, N
@dataclass
class InstrumentModuleBluePrint:
"""Spec necessary for creating instrument proxies."""
+
name: str
path: str
base_class: str
instrument_module_class: str
- docstring: str = ''
+ docstring: str = ""
parameters: Optional[Dict[str, ParameterBluePrint]] = field(default_factory=dict)
methods: Optional[Dict[str, MethodBluePrint]] = field(default_factory=dict)
- submodules: Optional[Dict[str, "InstrumentModuleBluePrint"]] = field(default_factory=dict)
- _class_type: str = 'InstrumentModuleBluePrint'
-
- def __init__(self, name: str,
- path: str,
- base_class: str,
- instrument_module_class: str,
- docstring: str = '',
- parameters: Optional[Dict[str, ParameterBluePrint]] = None,
- methods: Optional[Dict[str, MethodBluePrint]] = None,
- submodules: Optional[Dict[str, "InstrumentModuleBluePrint"]] = None,
- _class_type: str = 'InstrumentModuleBluePrint'):
+ submodules: Optional[Dict[str, "InstrumentModuleBluePrint"]] = field(
+ default_factory=dict
+ )
+ _class_type: str = "InstrumentModuleBluePrint"
+
+ def __init__(
+ self,
+ name: str,
+ path: str,
+ base_class: str,
+ instrument_module_class: str,
+ docstring: str = "",
+ parameters: Optional[Dict[str, ParameterBluePrint]] = None,
+ methods: Optional[Dict[str, MethodBluePrint]] = None,
+ submodules: Optional[Dict[str, "InstrumentModuleBluePrint"]] = None,
+ _class_type: str = "InstrumentModuleBluePrint",
+ ):
self.name = name
self.path = path
@@ -255,7 +267,7 @@ def __init__(self, name: str,
else:
raise AttributeError("parameters has invalid type.")
- self._class_type = 'InstrumentModuleBluePrint'
+ self._class_type = "InstrumentModuleBluePrint"
def __repr__(self) -> str:
return str(self)
@@ -263,40 +275,42 @@ def __repr__(self) -> str:
def __str__(self) -> str:
return f"{self.name}: {self.instrument_module_class}"
- def tostr(self, indent=0):
- i = indent * ' '
+ def tostr(self, indent: int = 0) -> str:
+ i = indent * " "
ret = f"""{i}{self.name}: {self.instrument_module_class}
{i}- path: {self.path}
{i}- base class: {self.base_class}
"""
ret += f"{i}- Parameters:\n{i} -----------\n"
- for pn, p in self.parameters.items():
+ for pn, p in self.parameters.items(): # type: ignore[union-attr]
ret += f"{i} - " + p.tostr(indent + 4)
ret += f"{i}- Methods:\n{i} --------\n"
- for mn, m in self.methods.items():
+ for mn, m in self.methods.items(): # type: ignore[union-attr]
ret += f"{i} - " + m.tostr(indent + 4)
ret += f"{i}- Submodules:\n{i} -----------\n"
- for sn, s in self.submodules.items():
+ for sn, s in self.submodules.items(): # type: ignore[union-attr]
ret += f"{i} - " + s.tostr(indent + 4)
return ret
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
return bluePrintToDict(self)
-def bluePrintFromInstrumentModule(path: str, ins: InstrumentModuleType) -> \
- Union[InstrumentModuleBluePrint, None]:
+def bluePrintFromInstrumentModule(
+ path: str, ins: InstrumentModuleType
+) -> Union[InstrumentModuleBluePrint, None]:
base_class = None
for bc in INSTRUMENT_MODULE_BASE_CLASSES:
if isinstance(ins, bc):
base_class = bc
break
if base_class is None:
- logger.warning(f"Blueprints for instrument base type of {ins} are "
- f"currently not supported.")
+ logger.warning(
+ f"Blueprints for instrument base type of {ins} are currently not supported."
+ )
return None
bp = InstrumentModuleBluePrint(
@@ -320,7 +334,7 @@ def bluePrintFromInstrumentModule(path: str, ins: InstrumentModuleType) -> \
for elt in dir(ins):
# don't include private methods, or methods that belong to the qcodes
# base classes.
- if elt[0] == '_' or hasattr(base_class, elt):
+ if elt[0] == "_" or hasattr(base_class, elt):
continue
o = getattr(ins, elt)
if callable(o) and not isinstance(o, tuple(PARAMETER_BASE_CLASSES)):
@@ -342,41 +356,45 @@ def bluePrintFromInstrumentModule(path: str, ins: InstrumentModuleType) -> \
@dataclass
class ParameterBroadcastBluePrint:
"""Blueprint to broadcast parameter changes."""
+
name: str
action: str
value: int | None = None
unit: str = ""
- _class_type: str = 'ParameterBroadcastBluePrint'
+ _class_type: str = "ParameterBroadcastBluePrint"
def __str__(self) -> str:
ret = f"""\"name\":\"{self.name}\": {{
\"action\":\"{self.action}" """
if self.value is not None:
- ret = ret + f"\n \"value\":\"{self.value}\""
+ ret = ret + f'\n "value":"{self.value}"'
if self.unit is not None:
- ret = ret + f"\n \"unit\":\"{self.unit}\""
- ret = ret + f"""\n}}"""
+ ret = ret + f'\n "unit":"{self.unit}"'
+ ret = ret + """\n}"""
return ret
- def __repr__(self):
+ def __repr__(self) -> str:
return str(self)
- def pprint(self, indent=0):
-
- i = indent * ' '
+ def pprint(self, indent: int = 0) -> str:
+ i = indent * " "
ret = f"""name: {self.name}
{i}- action: {self.action}
{i}- value: {self.value}
{i}- unit: {self.unit}
-{i}- bp_type: {self.bp_type}
"""
return ret
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
return bluePrintToDict(self)
-BluePrintType = Union[ParameterBluePrint, MethodBluePrint, InstrumentModuleBluePrint, ParameterBroadcastBluePrint]
+BluePrintType = Union[
+ ParameterBluePrint,
+ MethodBluePrint,
+ InstrumentModuleBluePrint,
+ ParameterBroadcastBluePrint,
+]
def _dictToJson(_dict: dict, json_type: bool = True) -> dict:
@@ -394,7 +412,7 @@ def _dictToJson(_dict: dict, json_type: bool = True) -> dict:
return ret
-def bluePrintToDict(bp: BluePrintType, json_type=True) -> dict:
+def bluePrintToDict(bp: BluePrintType, json_type: bool = True) -> dict:
"""
Converts a blueprint into a dictionary.
@@ -408,7 +426,9 @@ def bluePrintToDict(bp: BluePrintType, json_type=True) -> dict:
if isinstance(value, get_args(BluePrintType)):
bp_dict[my_field.name] = bluePrintToDict(value, json_type)
elif isinstance(value, dict):
- bp_dict[my_field.name] = _dictToJson(bp.__getattribute__(my_field.name), json_type)
+ bp_dict[my_field.name] = _dictToJson(
+ bp.__getattribute__(my_field.name), json_type
+ )
else:
if json_type:
bp_dict[my_field.name] = str(bp.__getattribute__(my_field.name))
@@ -422,25 +442,25 @@ class Operation(Enum):
"""Valid operations for the server."""
#: Get a list of instruments the server has instantiated.
- get_existing_instruments = 'get_existing_instruments'
+ get_existing_instruments = "get_existing_instruments"
#: Create a new instrument.
- create_instrument = 'create_instrument'
+ create_instrument = "create_instrument"
#: Get the blueprint of an object.
- get_blueprint = 'get_blueprint'
+ get_blueprint = "get_blueprint"
#: Make a call to an object.
- call = 'call'
+ call = "call"
#: Get the station contents as parameter dict.
- get_param_dict = 'get_param_dict'
+ get_param_dict = "get_param_dict"
#: Set station parameters from a dictionary.
- set_params = 'set_params'
+ set_params = "set_params"
#: Gets the GUI configuration for an instrument.
- get_gui_config = 'get_gui_config'
+ get_gui_config = "get_gui_config"
@dataclass
@@ -452,7 +472,7 @@ class InstrumentCreationSpec:
#: Name of the new instrument, I separate this from args and kwargs to
# make it easier to be found.
- name: str = ''
+ name: str = ""
#: Arguments to pass to the constructor.
args: Optional[Tuple] = None
@@ -460,12 +480,12 @@ class InstrumentCreationSpec:
#: kw args to pass to the constructor.
kwargs: Optional[Dict[str, Any]] = None
- _class_type: str = 'InstrumentCreationSpec'
+ _class_type: str = "InstrumentCreationSpec"
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
ret = asdict(self)
- ret['args'] = iterable_to_serialized_dict(self.args)
- ret['kwargs'] = dict_to_serialized_dict(self.kwargs)
+ ret["args"] = iterable_to_serialized_dict(self.args)
+ ret["kwargs"] = dict_to_serialized_dict(self.kwargs)
return ret
@@ -483,12 +503,12 @@ class CallSpec:
#: kw args to pass.
kwargs: Optional[Dict[str, Any]] = None
- _class_type: str = 'CallSpec'
+ _class_type: str = "CallSpec"
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
ret = asdict(self)
- ret['args'] = iterable_to_serialized_dict(self.args)
- ret['kwargs'] = dict_to_serialized_dict(self.kwargs)
+ ret["args"] = iterable_to_serialized_dict(self.args)
+ ret["kwargs"] = dict_to_serialized_dict(self.kwargs)
return ret
@@ -498,7 +518,7 @@ class ParameterSerializeSpec:
path: Optional[str] = None
#: Which attributes to include for each parameter. Default is ['values'].
- attrs: List[str] = field(default_factory=lambda: ['values'])
+ attrs: List[str] = field(default_factory=lambda: ["values"])
#: Additional arguments to pass to the serialization function
#: :func:`.serialize.toParamDict`.
@@ -508,12 +528,12 @@ class ParameterSerializeSpec:
#: :func:`.serialize.toParamDict`.
kwargs: Optional[Dict[str, Any]] = field(default_factory=dict)
- _class_type: str = 'ParameterSerializeSpec'
+ _class_type: str = "ParameterSerializeSpec"
- def toJson(self):
+ def toJson(self) -> Dict[str, Any]:
ret = asdict(self)
- ret['args'] = iterable_to_serialized_dict(self.args)
- ret['kwargs'] = dict_to_serialized_dict(self.kwargs)
+ ret["args"] = iterable_to_serialized_dict(self.args)
+ ret["kwargs"] = dict_to_serialized_dict(self.kwargs)
return ret
@@ -578,48 +598,48 @@ class ServerInstruction:
#: Generic keyword arguments.
kwargs: Optional[Dict[str, Any]] = field(default_factory=dict)
- _class_type: str = 'ServerInstruction'
+ _class_type: str = "ServerInstruction"
- def validate(self):
+ def validate(self) -> None:
if self.operation is Operation.create_instrument:
if not isinstance(self.create_instrument_spec, InstrumentCreationSpec):
- raise ValueError('Invalid instrument creation spec.')
+ raise ValueError("Invalid instrument creation spec.")
if self.operation is Operation.call:
if not isinstance(self.call_spec, CallSpec):
- raise ValueError('Invalid call spec.')
+ raise ValueError("Invalid call spec.")
if self.operation is Operation.get_gui_config:
if not isinstance(self.requested_path, str):
- raise ValueError('Invalid requested path.')
+ raise ValueError("Invalid requested path.")
- def toJson(self):
- ret = {'operation': str(self.operation.name)}
+ def toJson(self) -> Dict[str, Any]:
+ ret: Dict[str, Any] = {"operation": str(self.operation.name)}
if self.create_instrument_spec is None:
- ret['create_instrument_spec'] = None
+ ret["create_instrument_spec"] = None
else:
- ret['create_instrument_spec'] = self.create_instrument_spec.toJson()
+ ret["create_instrument_spec"] = self.create_instrument_spec.toJson()
if self.call_spec is None:
- ret['call_spec'] = None
+ ret["call_spec"] = None
else:
- ret['call_spec'] = self.call_spec.toJson()
+ ret["call_spec"] = self.call_spec.toJson()
if self.requested_path is None:
- ret['requested_path'] = None
+ ret["requested_path"] = None
else:
- ret['requested_path'] = str(self.requested_path)
+ ret["requested_path"] = str(self.requested_path)
if self.serialization_opts is None:
- ret['serialization_opts'] = None
+ ret["serialization_opts"] = None
else:
- ret['serialization_opts'] = self.serialization_opts.toJson()
+ ret["serialization_opts"] = self.serialization_opts.toJson()
- ret['set_parameters'] = self.set_parameters
- ret['args'] = iterable_to_serialized_dict(self.args)
- ret['kwargs'] = dict_to_serialized_dict(self.kwargs)
- ret['_class_type'] = self._class_type
+ ret["set_parameters"] = self.set_parameters
+ ret["args"] = iterable_to_serialized_dict(self.args)
+ ret["kwargs"] = dict_to_serialized_dict(self.kwargs)
+ ret["_class_type"] = self._class_type
return ret
@@ -635,6 +655,7 @@ class ServerResponse:
If an error occurs, `message` is typically ``None``, and `error` contains an
error message or object describing the error.
"""
+
#: The return message.
message: Optional[Any] = None
@@ -642,11 +663,14 @@ class ServerResponse:
error: Optional[Union[None, str, Warning, Exception]] = None
#: The type of the class, used for deserializing it.
- _class_type: str = 'ServerResponse'
-
- def __init__(self, message: Optional[Any] = None,
- error: Optional[Union[None, str, Warning, Exception, dict]] = None,
- _class_type: str = 'ServerResponse'):
+ _class_type: str = "ServerResponse"
+
+ def __init__(
+ self,
+ message: Optional[Any] = None,
+ error: Optional[Union[None, str, Warning, Exception, dict]] = None,
+ _class_type: str = "ServerResponse",
+ ):
self.message = message
if isinstance(message, str):
try:
@@ -661,36 +685,40 @@ def __init__(self, message: Optional[Any] = None,
message = message.replace("none", "null")
after_json_loads = json.loads(message)
self.message = after_json_loads
- except json.JSONDecodeError as e:
- logger.debug(f'message could not be decoded by JSON and will be treated as a string: {message}')
+ except json.JSONDecodeError:
+ logger.debug(
+ f"message could not be decoded by JSON and will be treated as a string: {message}"
+ )
if isinstance(error, dict):
- self.error = Exception(error['message'])
+ self.error = Exception(error["message"])
else:
self.error = error
- self._class_type = 'ServerResponse'
+ self._class_type = "ServerResponse"
- def toJson(self):
- ret = {}
+ def toJson(self) -> Dict[str, Any]:
+ ret: Dict[str, Any] = {}
if isinstance(self.message, get_args(BluePrintType)):
- ret['message'] = self.message.toJson()
- elif hasattr(self.message, 'attributes'):
- ret['message'] = _convert_arbitrary_obj_to_dict(self.message)
+ ret["message"] = self.message.toJson()
+ elif hasattr(self.message, "attributes"):
+ ret["message"] = _convert_arbitrary_obj_to_dict(self.message)
elif not isinstance(self.message, str) and isinstance(self.message, Iterable):
if isinstance(self.message, dict):
message_dict = dict_to_serialized_dict(self.message)
- ret['message'] = str(message_dict)
+ ret["message"] = str(message_dict)
else:
message_iterable = iterable_to_serialized_dict(self.message)
- ret['message'] = str(message_iterable)
+ ret["message"] = str(message_iterable)
else:
- ret['message'] = str(self.message)
+ ret["message"] = str(self.message)
if isinstance(self.error, Exception):
- ret['error'] = dict(exception_type=str(type(self.error)), message=str(self.error))
+ ret["error"] = dict(
+ exception_type=str(type(self.error)), message=str(self.error)
+ )
else:
- ret['error'] = str(self.error)
+ ret["error"] = str(self.error)
- ret['_class_type'] = self._class_type
+ ret["_class_type"] = self._class_type
return ret
@@ -702,13 +730,13 @@ def _convert_arbitrary_obj_to_dict(obj: object) -> Dict[str, Any]:
all of those attributes are natively JSON serializable. These should also be accepted as keyword arguments in the
constructor of the object.
"""
- if not hasattr(obj, 'attributes'):
+ if not hasattr(obj, "attributes"):
raise AttributeError('Object does not have an attribute called "attributes"')
obj_dict = {}
for attr in obj.attributes:
obj_dict[attr] = getattr(obj, attr)
- obj_dict['_class_type'] = f'{obj.__module__}.{obj.__class__.__name__}'
+ obj_dict["_class_type"] = f"{obj.__module__}.{obj.__class__.__name__}"
return obj_dict
@@ -719,27 +747,29 @@ def _convert_dict_to_obj(item_dict: dict) -> Any:
Assumes that the dictionary has a key '_class_type' indicating what class it should be instantiated from.
"""
- class_type = item_dict['_class_type']
+ class_type = item_dict["_class_type"]
# if a dot is present indicates the class is arbitrary and needs to be imported
- if '.' in class_type:
- parts = class_type.split('.')
- mod = importlib.import_module('.'.join(parts[:-1]))
+ if "." in class_type:
+ parts = class_type.split(".")
+ mod = importlib.import_module(".".join(parts[:-1]))
cls = getattr(mod, parts[-1])
- item_dict.pop('_class_type')
+ item_dict.pop("_class_type")
return cls(**item_dict)
try:
- instantiated_obj = eval(f'{class_type}(**item_dict)')
+ instantiated_obj = eval(f"{class_type}(**item_dict)")
# built-ins (like complex) will not want the _class_type argument
except TypeError:
- cls = item_dict.pop('_class_type')
- instantiated_obj = eval(f'{cls}(**item_dict)')
+ cls = item_dict.pop("_class_type")
+ instantiated_obj = eval(f"{cls}(**item_dict)")
return instantiated_obj
-def iterable_to_serialized_dict(iterable: Optional[Iterable[Any]] = None):
+def iterable_to_serialized_dict(
+ iterable: Optional[Iterable[Any]] = None,
+) -> Any:
"""
Goes through an iterable (lists, tuples, sets) and serialize each object inside of it. If trying to serialize an
arbitrary object, this object must have a class attribute "attributes" for the serialization to happen correctly.
@@ -766,24 +796,30 @@ def iterable_to_serialized_dict(iterable: Optional[Iterable[Any]] = None):
serialized_iterable = iterable_to_serialized_dict(iterable=item)
converted_iterable.append(serialized_iterable)
- elif hasattr(item, 'attributes'):
+ elif hasattr(item, "attributes"):
arg_dict = _convert_arbitrary_obj_to_dict(item)
converted_iterable.append(arg_dict)
elif isinstance(item, complex):
- arg_dict = dict(real=item.real, imag=item.imag, _class_type='complex')
+ arg_dict = dict(
+ real=float(item.real), imag=float(item.imag), _class_type="complex"
+ )
converted_iterable.append(arg_dict)
else:
converted_iterable.append(str(item))
if isinstance(iterable, np.ndarray):
- converted_iterable = dict(object=converted_iterable, _class_type="numpy.array")
+ converted_iterable = dict(
+ object=converted_iterable, _class_type="numpy.array"
+ )
return converted_iterable
-def dict_to_serialized_dict(dct: Optional[Dict[str, Any]] = None):
+def dict_to_serialized_dict(
+ dct: Optional[Dict[str, Any]] = None,
+) -> Any:
"""
Same idea as iterable_to_serialized_dict but for dictionaries.
"""
@@ -802,11 +838,15 @@ def dict_to_serialized_dict(dct: Optional[Dict[str, Any]] = None):
serialized_iterable = iterable_to_serialized_dict(iterable=value)
converted_dict[name] = serialized_iterable
- elif hasattr(value, 'attributes'):
+ elif hasattr(value, "attributes"):
kwarg_dict = _convert_arbitrary_obj_to_dict(value)
converted_dict[name] = kwarg_dict
elif isinstance(value, complex):
- kwarg_dict = dict(real=value.real, imag=value.imag, _class_type='complex')
+ kwarg_dict = dict(
+ real=float(value.real),
+ imag=float(value.imag),
+ _class_type="complex",
+ )
converted_dict[name] = kwarg_dict
else:
converted_dict[name] = str(value)
@@ -814,7 +854,7 @@ def dict_to_serialized_dict(dct: Optional[Dict[str, Any]] = None):
return converted_dict
-def to_dict(data) -> Union[Dict[str, str], str]:
+def to_dict(data: Any) -> Union[Dict[str, str], str]:
"""
Converts object to json serializable. This is done by calling the method toJson of the object being passed.
Strings are returned without any more processing.
@@ -825,12 +865,12 @@ def to_dict(data) -> Union[Dict[str, str], str]:
return data.toJson()
-def _is_numeric(val) -> Optional[Union[float, complex]]:
+def _is_numeric(val: Any) -> Optional[Union[float, complex]]:
"""
Tries to convert the input into a int or a float. If it can, returns the conversion. Otherwise returns None.
"""
try:
- if val is not None and not '.' in val:
+ if val is not None and "." not in val:
int_conversion = int(val)
return int_conversion
except Exception:
@@ -851,20 +891,20 @@ def _is_numeric(val) -> Optional[Union[float, complex]]:
return None
-def deserialize_obj(data: Any):
+def deserialize_obj(data: Any) -> Any:
"""
Tries to deserialize any object. If the object is a dictionary and contains the key '_class_type' it means that
that dictionary represents a serialized object that needs to be instantiated. The function will try and deserailize
any other item in the dictionary.
"""
- if data is None or data == 'None':
+ if data is None or data == "None":
return None
elif isinstance(data, dict):
deserialized_dict = {}
for key, value in data.items():
deserialized_dict[key] = deserialize_obj(value)
- if '_class_type' in deserialized_dict:
+ if "_class_type" in deserialized_dict:
obj_instance = _convert_dict_to_obj(deserialized_dict)
return obj_instance
@@ -873,24 +913,26 @@ def deserialize_obj(data: Any):
numeric_form = _is_numeric(data)
if numeric_form is not None:
return numeric_form
- elif data == 'True':
+ elif data == "True":
return True
- elif data == 'False':
+ elif data == "False":
return False
- elif data == '{}':
+ elif data == "{}":
return {}
- elif data == '[]':
+ elif data == "[]":
return []
elif isinstance(data, str):
if len(data) > 0:
# Try and load other items in the string it since it might be a nested item
- if (data[0] == '{' and data[-1] == '}') or (data[0] == '[' and data[-1] == ']'):
+ if (data[0] == "{" and data[-1] == "}") or (
+ data[0] == "[" and data[-1] == "]"
+ ):
try:
loaded_json = json.loads(data.replace("'", '"'))
return deserialize_obj(loaded_json)
- except json.JSONDecodeError as e:
- logger.debug('str could not be decoded, treating it as a str')
+ except json.JSONDecodeError:
+ logger.debug("str could not be decoded, treating it as a str")
return data
@@ -900,4 +942,3 @@ def deserialize_obj(data: Any):
deserialized_iterable.append(deserialize_obj(item))
# Returns the same type of iterable
return deserialized_iterable
-
diff --git a/src/instrumentserver/client/__init__.py b/src/instrumentserver/client/__init__.py
new file mode 100644
index 0000000..7253c60
--- /dev/null
+++ b/src/instrumentserver/client/__init__.py
@@ -0,0 +1,6 @@
+from .core import sendRequest as sendRequest
+from .proxy import Client as Client
+from .proxy import ClientStation as ClientStation
+from .proxy import ProxyInstrument as ProxyInstrument
+from .proxy import QtClient as QtClient
+from .proxy import SubClient as SubClient
diff --git a/instrumentserver/client/application.py b/src/instrumentserver/client/application.py
similarity index 70%
rename from instrumentserver/client/application.py
rename to src/instrumentserver/client/application.py
index cd6b5cc..9c04582 100644
--- a/instrumentserver/client/application.py
+++ b/src/instrumentserver/client/application.py
@@ -1,24 +1,21 @@
-from pathlib import Path
-from typing import Optional, Union
-import sys
-import json
-import logging
import importlib
+import logging
+import sys
+from pathlib import Path
+from typing import Dict, Optional, Union, cast
-from qcodes import Instrument
-from qtpy.QtWidgets import QFileDialog, QMenu, QWidget, QSizePolicy, QSplitter
from qtpy.QtGui import QGuiApplication
-from qtpy.QtCore import Qt
+from qtpy.QtWidgets import QFileDialog, QWidget
-from instrumentserver import QtCore, QtWidgets, QtGui, getInstrumentserverPath
-from instrumentserver.client import QtClient, Client, ClientStation
+from instrumentserver import QtCore, QtGui, QtWidgets, getInstrumentserverPath
+from instrumentserver.blueprints import ParameterBroadcastBluePrint
+from instrumentserver.client import ClientStation
from instrumentserver.client.proxy import SubClient
from instrumentserver.gui.instruments import GenericInstrument
from instrumentserver.gui.misc import DetachableTabWidget
-from instrumentserver.log import LogLevels, LogWidget, log
+from instrumentserver.log import LogWidget
from instrumentserver.log import logger as get_instrument_logger
from instrumentserver.server.application import StationList, StationObjectInfo
-from instrumentserver.blueprints import ParameterBroadcastBluePrint
# instrument class key in configuration files for configurations that will be applied to all instruments
DEFAULT_INSTRUMENT_KEY = "__default__"
@@ -28,7 +25,11 @@
class ServerWidget(QtWidgets.QWidget):
- def __init__(self, client_station:ClientStation, parent=None):
+ def __init__(
+ self,
+ client_station: ClientStation,
+ parent: Optional[QtWidgets.QWidget] = None,
+ ) -> None:
super().__init__(parent)
self.client_station = client_station
@@ -37,8 +38,16 @@ def __init__(self, client_station:ClientStation, parent=None):
form_layout = QtWidgets.QFormLayout(form)
form_layout.setContentsMargins(0, 0, 0, 0)
form_layout.setSpacing(8)
- form_layout.setLabelAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
- form_layout.setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)
+ form_layout.setLabelAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignRight
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
+ )
+ )
+ form_layout.setFieldGrowthPolicy(
+ QtWidgets.QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow
+ )
# Non-editable
self.host = QtWidgets.QLineEdit(self.client_station._host)
@@ -58,7 +67,9 @@ def __init__(self, client_station:ClientStation, parent=None):
lh = self.cmd.fontMetrics().lineSpacing()
self.cmd.setFixedHeight(lh * rows + 2 * self.cmd.frameWidth() + 8)
# Let it grow horizontally
- self.cmd.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
+ self.cmd.setSizePolicy(
+ QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed
+ )
form_layout.addRow("Host:", self.host)
form_layout.addRow("Port:", self.port)
@@ -81,16 +92,15 @@ def __init__(self, client_station:ClientStation, parent=None):
# main.addLayout(btns)
main.addStretch(1)
- def _tint_readonly(self, le, bg="#f3f6fa"):
+ def _tint_readonly(self, le: QtWidgets.QLineEdit, bg: str = "#f3f6fa") -> None:
pal = le.palette()
pal.setColor(QtGui.QPalette.Base, QtGui.QColor(bg))
le.setPalette(pal)
# def restart_server(self):
- # todo: to be implemented, ssh to server pc and start the server there.
- # need to close the port if occupied.
- # print(self.cmd.toPlainText())
-
+ # todo: to be implemented, ssh to server pc and start the server there.
+ # need to close the port if occupied.
+ # print(self.cmd.toPlainText())
class ClientStationGui(QtWidgets.QMainWindow):
@@ -106,23 +116,32 @@ def __init__(self, station: ClientStation):
# Set unique Windows App ID so that this app can have separate taskbar entry than other Qt apps
if sys.platform == "win32":
import ctypes
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("InstrumentServer.ClientStation")
- self.setWindowIcon(QtGui.QIcon(getInstrumentserverPath("resource","icons")+"/client_app_icon.svg"))
+
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
+ "InstrumentServer.ClientStation"
+ )
+ self.setWindowIcon(
+ QtGui.QIcon(
+ getInstrumentserverPath("resource", "icons") + "/client_app_icon.svg"
+ )
+ )
self.station = station
self.cli = station.client
# set up the listener thread and worker that listens to update messages emitted by the server (from all clients)
self.listenerThread = QtCore.QThread()
- self.listener = SubClient(instruments=None, sub_host=self.cli.host, sub_port=self.cli.port+1)
+ self.listener = SubClient(
+ instruments=None, sub_host=self.cli.host, sub_port=self.cli.port + 1
+ )
self.listener.moveToThread(self.listenerThread)
- self.listenerThread.started.connect(self.listener.connect)
+ self.listenerThread.started.connect(self.listener.connect) # type: ignore[arg-type]
self.listener.finished.connect(self.listenerThread.quit)
self.listener.finished.connect(self.listener.deleteLater)
self.listener.finished.connect(self.listenerThread.deleteLater)
self.listener.update.connect(self.listenerEvent)
self.listenerThread.start()
- self.instrumentTabsOpen = {}
+ self.instrumentTabsOpen: Dict[str, QtWidgets.QWidget] = {}
# --- main tabs
self.tabs = DetachableTabWidget()
@@ -131,8 +150,8 @@ def __init__(self, station: ClientStation):
self.tabs.currentChanged.connect(self.onTabChanged)
# --- client station
- self.stationList = StationList() # instrument list
- self.stationObjInfo = StationObjectInfo() # instrument docs
+ self.stationList = StationList() # instrument list
+ self.stationObjInfo = StationObjectInfo() # instrument docs
for inst in self.station.instruments.values():
self.stationList.addInstrument(inst.bp)
@@ -140,36 +159,35 @@ def __init__(self, station: ClientStation):
self.stationList.itemDoubleClicked.connect(self.openInstrumentTab)
self.stationList.closeRequested.connect(self.closeInstrument)
- stationWidgets = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
+ stationWidgets = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
stationWidgets.addWidget(self.stationList)
stationWidgets.addWidget(self.stationObjInfo)
stationWidgets.setSizes([200, 500])
- self.tabs.addUnclosableTab(stationWidgets, 'Station')
+ self.tabs.addUnclosableTab(stationWidgets, "Station")
self.addParameterLoadSaveToolbar()
# --- log widget
self.log_widget = LogWidget(level=logging.INFO)
- self.tabs.addUnclosableTab(self.log_widget, 'Log')
+ self.tabs.addUnclosableTab(self.log_widget, "Log")
# --- server widget
self.server_widget = ServerWidget(self.station)
- self.tabs.addUnclosableTab(self.server_widget, 'Server')
-
+ self.tabs.addUnclosableTab(self.server_widget, "Server")
# adjust window size
- screen_geometry = QGuiApplication.primaryScreen().availableGeometry()
+ screen_geometry = QGuiApplication.primaryScreen().availableGeometry() # type: ignore[union-attr]
width = int(screen_geometry.width() * 0.3) # 30% of screen width
height = int(screen_geometry.height() * 0.7) # 70% of screen height
self.resize(width, height)
@QtCore.Slot(ParameterBroadcastBluePrint)
- def listenerEvent(self, message: ParameterBroadcastBluePrint):
- if message.action == 'parameter-update':
+ def listenerEvent(self, message: ParameterBroadcastBluePrint) -> None:
+ if message.action == "parameter-update":
logger.info(f"{message.action}: {message.name}: {message.value}")
- def openInstrumentTab(self, item: QtWidgets.QListWidgetItem, index: int):
+ def openInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int) -> None:
"""
Gets called when the user double clicks and item of the instrument list.
Adds a new generic instrument GUI window to the tab bar.
@@ -181,25 +199,32 @@ def openInstrumentTab(self, item: QtWidgets.QListWidgetItem, index: int):
# Get GUI config from station config (patterns already merged by config.py)
instrument_config = self.station.full_config.get(name, {})
- gui_config = instrument_config.get('gui', {})
- gui_kwargs = gui_config.get('kwargs', {})
+ gui_config = instrument_config.get("gui", {})
+ gui_kwargs = gui_config.get("kwargs", {})
widgetClass = GenericInstrument
# Check if a custom GUI type is specified
- if 'type' in gui_config:
+ if "type" in gui_config:
try:
# import the widget
- moduleName = '.'.join(gui_config['type'].split('.')[:-1])
- widgetClassName = gui_config['type'].split('.')[-1]
+ moduleName = ".".join(gui_config["type"].split(".")[:-1])
+ widgetClassName = gui_config["type"].split(".")[-1]
module = importlib.import_module(moduleName)
widgetClass = getattr(module, widgetClassName)
except (ImportError, AttributeError) as e:
- logger.warning(f"Failed to load custom GUI '{gui_config['type']}' for '{name}': {e}. Using default GenericInstrument.")
+ logger.warning(
+ f"Failed to load custom GUI '{gui_config['type']}' for '{name}': {e}. Using default GenericInstrument."
+ )
widgetClass = GenericInstrument
- ins_widget = widgetClass(instrument, parent=self, sub_host=self.cli.host, sub_port=self.cli.port+1,
- **gui_kwargs)
+ ins_widget = widgetClass(
+ instrument,
+ parent=self,
+ sub_host=self.cli.host,
+ sub_port=self.cli.port + 1,
+ **gui_kwargs,
+ )
# add tab
ins_widget.setObjectName(name)
@@ -211,7 +236,7 @@ def openInstrumentTab(self, item: QtWidgets.QListWidgetItem, index: int):
self.tabs.setCurrentWidget(self.instrumentTabsOpen[name])
@QtCore.Slot(str)
- def _displayComponentInfo(self, name: Union[str, None]):
+ def _displayComponentInfo(self, name: Union[str, None]) -> None:
if name is not None:
bp = self.station[name].bp
else:
@@ -219,23 +244,27 @@ def _displayComponentInfo(self, name: Union[str, None]):
self.stationObjInfo.setObject(bp)
@QtCore.Slot(int)
- def onTabChanged(self, index):
+ def onTabChanged(self, index: int) -> None:
widget = self.tabs.widget(index)
# if instrument tab is not in 'instrumentTabsOpen' yet, tab must be just open, in this case the constructor
# of the parameter widget should have already called refresh, so we don't have to do that again.
- if hasattr(widget, "parametersList") and (widget.objectName() in self.instrumentTabsOpen):
- widget.parametersList.model.refreshAll()
+ if hasattr(widget, "parametersList") and (
+ widget.objectName() in self.instrumentTabsOpen # type: ignore[union-attr]
+ ):
+ widget.parametersList.model.refreshAll() # type: ignore[union-attr]
@QtCore.Slot(str)
def onTabDeleted(self, name: str) -> None:
if name in self.instrumentTabsOpen:
del self.instrumentTabsOpen[name]
- def addParameterLoadSaveToolbar(self):
+ def addParameterLoadSaveToolbar(self) -> None:
# --- toolbar basics ---
self.toolBar = QtWidgets.QToolBar("Params", self)
self.toolBar.setIconSize(QtCore.QSize(22, 22))
- self.toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
+ self.toolBar.setToolButtonStyle(
+ QtCore.Qt.ToolButtonStyle.ToolButtonTextBesideIcon
+ )
self.addToolBar(self.toolBar)
# --- composite path widget
@@ -248,7 +277,7 @@ def addParameterLoadSaveToolbar(self):
self.paramPathEdit = QtWidgets.QLineEdit(pathWidget)
self.paramPathEdit.setPlaceholderText("Parameter file path")
- self.paramPathEdit.setText(str(Path.cwd()/"client_params.json"))
+ self.paramPathEdit.setText(str(Path.cwd() / "client_params.json"))
self.paramPathEdit.setClearButtonEnabled(True)
self.paramPathEdit.setMinimumWidth(280)
h = self.paramPathEdit.fontMetrics().height() + 10
@@ -260,7 +289,7 @@ def addParameterLoadSaveToolbar(self):
pathLayout.addWidget(lbl)
pathLayout.addWidget(self.paramPathEdit, 1) # stretch
-
+
pathAction = QtWidgets.QWidgetAction(self.toolBar)
pathAction.setDefaultWidget(pathWidget)
self.toolBar.addAction(pathAction)
@@ -272,7 +301,7 @@ def addParameterLoadSaveToolbar(self):
saveAct = QtWidgets.QAction(QtGui.QIcon(":/icons/save.svg"), "Save", self)
loadAct.triggered.connect(self.loadParams)
saveAct.triggered.connect(self.saveParams)
-
+
self.toolBar.addAction(browseBtn)
self.toolBar.addAction(loadAct)
self.toolBar.addAction(saveAct)
@@ -281,7 +310,7 @@ def addParameterLoadSaveToolbar(self):
self.paramPathEdit.returnPressed.connect(self.loadParams)
@QtCore.Slot()
- def browseParamPath(self):
+ def browseParamPath(self) -> None:
filePath, _ = QFileDialog.getOpenFileName(
self, "Select Parameter File", ".", "JSON Files (*.json);;All Files (*)"
)
@@ -289,10 +318,12 @@ def browseParamPath(self):
self.paramPathEdit.setText(filePath)
@QtCore.Slot()
- def saveParams(self):
+ def saveParams(self) -> None:
file_path = self.paramPathEdit.text()
if not file_path:
- QtWidgets.QMessageBox.warning(self, "No file path", "Please specify a path to save parameters.")
+ QtWidgets.QMessageBox.warning(
+ self, "No file path", "Please specify a path to save parameters."
+ )
return
try:
self.station.save_parameters(file_path)
@@ -301,10 +332,12 @@ def saveParams(self):
QtWidgets.QMessageBox.critical(self, "Save Error", str(e))
@QtCore.Slot()
- def loadParams(self):
+ def loadParams(self) -> None:
file_path = self.paramPathEdit.text()
if not file_path:
- QtWidgets.QMessageBox.warning(self, "No file path", "Please specify a path to load parameters.")
+ QtWidgets.QMessageBox.warning(
+ self, "No file path", "Please specify a path to load parameters."
+ )
return
try:
self.station.load_parameters(file_path)
@@ -313,20 +346,28 @@ def loadParams(self):
# Refresh all tabs
for i in range(self.tabs.count()):
widget = self.tabs.widget(i)
- if hasattr(widget, 'parametersList') and hasattr(widget.parametersList, 'model'):
+ if (
+ widget is not None
+ and hasattr(widget, "parametersList")
+ and hasattr(
+ widget.parametersList,
+ "model",
+ )
+ ):
widget.parametersList.model.refreshAll()
except Exception as e:
QtWidgets.QMessageBox.critical(self, "Load Error", str(e))
-
@QtCore.Slot(str)
- def closeInstrument(self, name: str):#, item: QtWidgets.QListWidgetItem):
+ def closeInstrument(self, name: str) -> None: # , item: QtWidgets.QListWidgetItem):
try:
# close instrument on server
self.station.close_instrument(name)
except Exception as e:
- QtWidgets.QMessageBox.critical(self, "Close Error", f"Failed to close '{name}':\n{e}")
+ QtWidgets.QMessageBox.critical(
+ self, "Close Error", f"Failed to close '{name}':\n{e}"
+ )
return
# remove from gui
@@ -334,8 +375,7 @@ def closeInstrument(self, name: str):#, item: QtWidgets.QListWidgetItem):
logger.info(f"Closed instrument '{name}'")
-
- def removeInstrumentFromGui(self, name: str):
+ def removeInstrumentFromGui(self, name: str) -> None:
"""Remove an instrument from the station list."""
self.stationList.removeObject(name)
self.stationObjInfo.clear()
@@ -343,8 +383,15 @@ def removeInstrumentFromGui(self, name: str):
self.tabs.removeTab(self.tabs.indexOf(self.instrumentTabsOpen[name]))
del self.instrumentTabsOpen[name]
- def closeEvent(self, event: QtGui.QCloseEvent) -> None:
+ def closeEvent(self, event: QtGui.QCloseEvent) -> None: # type: ignore[override]
"""Cleanup listener thread before closing the window."""
+ for name, widget in list(self.instrumentTabsOpen.items()):
+ try:
+ widget.close()
+ except Exception:
+ pass
+ self.instrumentTabsOpen.clear()
+
self.listener.stop()
self.listenerThread.quit()
self.listenerThread.wait(3000) # Wait up to 3 seconds for thread to finish
diff --git a/instrumentserver/client/core.py b/src/instrumentserver/client/core.py
similarity index 60%
rename from instrumentserver/client/core.py
rename to src/instrumentserver/client/core.py
index 27f65fb..e55a980 100644
--- a/instrumentserver/client/core.py
+++ b/src/instrumentserver/client/core.py
@@ -1,17 +1,18 @@
import logging
+import uuid
import warnings
+from types import TracebackType
+from typing import Any, Optional, Type
+
import zmq
-import uuid
from instrumentserver import DEFAULT_PORT
-from instrumentserver.base import send, recv
+from instrumentserver.base import recv, send
from instrumentserver.server.core import ServerResponse
-
logger = logging.getLogger(__name__)
-
class BaseClient:
"""Simple client for the StationServer.
When a timeout happens, a RunTimeError is being raised. This error is there just to warn the user that a timeout
@@ -25,10 +26,18 @@ class BaseClient:
:param raise_exceptions: If true the client will raise an exception when the server sends one to it, defaults to True.
"""
- def __init__(self, host='localhost', port=DEFAULT_PORT, connect=True, timeout=20, raise_exceptions=True):
+ def __init__(
+ self,
+ host: str = "localhost",
+ port: int = DEFAULT_PORT,
+ connect: bool = True,
+ timeout: float = 20,
+ raise_exceptions: bool = True,
+ ) -> None:
self.connected = False
- self.context = None
- self.socket = None
+ self._closed = False
+ self.context: Optional[zmq.Context] = None
+ self.socket: Optional[zmq.Socket] = None
self.host = host
self.port = port
self.addr = f"tcp://{host}:{port}"
@@ -38,32 +47,55 @@ def __init__(self, host='localhost', port=DEFAULT_PORT, connect=True, timeout=20
if connect:
self.connect()
- def __enter__(self):
+ def __enter__(self) -> "BaseClient":
if not self.connected:
self.connect()
return self
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(
+ self,
+ exc_type: Optional[Type[BaseException]],
+ exc_val: Optional[BaseException],
+ exc_tb: Optional[TracebackType],
+ ) -> None:
self.disconnect()
- def connect(self):
+ def connect(self) -> None:
+ if self._closed:
+ raise RuntimeError("Client has been permanently disconnected.")
+ # Clean up any existing context/socket so we don't leak them when
+ # connect() is called more than once (e.g. EmbeddedClient.start()).
+ if self.socket is not None:
+ try:
+ self.socket.close(linger=0)
+ except Exception:
+ pass
+ self.socket = None
+ if self.context is not None:
+ try:
+ self.context.destroy(linger=0)
+ except Exception:
+ pass
+ self.context = None
logger.info(f"Connecting to {self.addr}")
self.context = zmq.Context()
self.socket = self.context.socket(zmq.DEALER)
self.socket.setsockopt(zmq.RCVTIMEO, self.recv_timeout_ms)
- self.socket.setsockopt(zmq.IDENTITY, uuid.uuid4().hex.encode()) #todo: more meaningful id?
+ self.socket.setsockopt(
+ zmq.IDENTITY, uuid.uuid4().hex.encode()
+ ) # todo: more meaningful id?
self.socket.connect(self.addr)
self.connected = True
- def ask(self, message):
- if not self.connected:
+ def ask(self, message: Any) -> Any:
+ if self._closed or not self.connected:
raise RuntimeError("No connection yet.")
# try so that if timeout happens, the client remains usable
try:
- send(self.socket, message)
- ret = recv(self.socket)
- logger.debug(f"Response received.")
+ send(self.socket, message) # type: ignore[arg-type]
+ ret = recv(self.socket) # type: ignore[arg-type]
+ logger.debug("Response received.")
logger.debug(f"Response: {str(ret)}")
except zmq.error.Again:
self._reset_connection()
@@ -72,7 +104,7 @@ def ask(self, message):
else:
logger.error("Server did not reply before timeout.")
return None
-
+
if isinstance(ret, ServerResponse):
err = ret.error
if err is not None:
@@ -80,16 +112,17 @@ def ask(self, message):
return ret.message
return ret
-
- def _reset_connection(self):
+
+ def _reset_connection(self) -> None:
try:
if self.socket is not None:
self.socket.close(linger=0)
finally:
self.connected = False
- self.connect()
-
- def _handle_server_error(self, err):
+ if not self._closed:
+ self.connect()
+
+ def _handle_server_error(self, err: Any) -> None:
if isinstance(err, str):
logger.error(err)
if self.raise_exceptions:
@@ -105,19 +138,25 @@ def _handle_server_error(self, err):
if self.raise_exceptions:
raise TypeError(msg)
logger.error(msg)
-
- def disconnect(self):
+
+ def disconnect(self) -> None:
+ self._closed = True
if self.socket is not None:
try:
self.socket.close(linger=0)
except Exception:
pass
self.socket = None
+ if self.context is not None:
+ try:
+ self.context.destroy(linger=0)
+ except Exception:
+ pass
+ self.context = None
self.connected = False
-def sendRequest(message, host='localhost', port=DEFAULT_PORT):
+def sendRequest(message: Any, host: str = "localhost", port: int = DEFAULT_PORT) -> Any:
with BaseClient(host, port) as cli:
ret = cli.ask(message)
return ret
-
diff --git a/instrumentserver/client/proxy.py b/src/instrumentserver/client/proxy.py
similarity index 66%
rename from instrumentserver/client/proxy.py
rename to src/instrumentserver/client/proxy.py
index 513f53e..3c43d74 100644
--- a/instrumentserver/client/proxy.py
+++ b/src/instrumentserver/client/proxy.py
@@ -4,37 +4,38 @@
@author: Chao
"""
+
+import collections
import inspect
import json
-import yaml
import logging
import os
-from types import MethodType
-import collections
-from typing import Any, Union, Optional, Dict, List
import threading
from contextlib import contextmanager
+from types import MethodType
+from typing import Any, Callable, Dict, List, Optional, Union
import qcodes as qc
import zmq
from qcodes import Instrument, Parameter
from qcodes.instrument.base import InstrumentBase
-from instrumentserver import QtCore, DEFAULT_PORT
+from instrumentserver import DEFAULT_PORT, QtCore
from instrumentserver.helpers import flat_to_nested_dict, flatten_dict, is_flat_dict
from instrumentserver.server.core import (
- ServerInstruction,
+ CallSpec,
+ InstrumentCreationSpec,
InstrumentModuleBluePrint,
- ParameterBluePrint,
MethodBluePrint,
- CallSpec,
Operation,
- InstrumentCreationSpec,
+ ParameterBluePrint,
ParameterSerializeSpec,
+ ServerInstruction,
)
-from .core import sendRequest, BaseClient
+
from ..base import recvMultipart
from ..blueprints import ParameterBroadcastBluePrint
+from .core import BaseClient, sendRequest
logger = logging.getLogger(__name__)
@@ -47,17 +48,20 @@
class ProxyMixin:
- """ A simple mixin class for proxy objects."""
-
- def __init__(self, *args,
- cli: Optional["Client"] = None,
- host: Optional[str] = 'localhost',
- port: Optional[int] = DEFAULT_PORT,
- remotePath: Optional[str] = None,
- bluePrint: Optional[Union[ParameterBluePrint,
- InstrumentModuleBluePrint,
- MethodBluePrint]] = None,
- **kwargs):
+ """A simple mixin class for proxy objects."""
+
+ def __init__(
+ self,
+ *args: Any,
+ cli: Optional["Client"] = None,
+ host: Optional[str] = "localhost",
+ port: Optional[int] = DEFAULT_PORT,
+ remotePath: Optional[str] = None,
+ bluePrint: Optional[
+ Union[ParameterBluePrint, InstrumentModuleBluePrint, MethodBluePrint]
+ ] = None,
+ **kwargs: Any,
+ ) -> None:
self.cli = cli
self.host = host
@@ -73,35 +77,31 @@ def __init__(self, *args,
self.bp = bluePrint
self.remotePath = self.bp.path
else:
- raise ValueError("Either `remotePath` or `bluePrint` must be "
- "specified.")
+ raise ValueError("Either `remotePath` or `bluePrint` must be specified.")
kwargs.update(self.initKwargsFromBluePrint(self.bp))
super().__init__(*args, **kwargs)
self.__doc__ = self.bp.docstring
- def initKwargsFromBluePrint(self, bp):
+ def initKwargsFromBluePrint(self, bp: Any) -> Dict[str, Any]:
raise NotImplementedError
- def askServer(self, message: ServerInstruction):
+ def askServer(self, message: ServerInstruction) -> Any:
if self.cli is not None:
return self.cli.ask(message)
elif self.host is not None and self.port is not None:
return sendRequest(message, self.host, self.port)
- def _getBluePrintFromServer(self, path):
- req = ServerInstruction(
- operation=Operation.get_blueprint,
- requested_path=path
- )
+ def _getBluePrintFromServer(self, path: str) -> Any:
+ req = ServerInstruction(operation=Operation.get_blueprint, requested_path=path)
return self.askServer(req)
- def get_snapshot(self, *args, **kwargs):
+ def get_snapshot(self, *args: Any, **kwargs: Any) -> Any:
req = ServerInstruction(
operation=Operation.call,
call_spec=CallSpec(
- target=self.remotePath + '.snapshot', args=args, kwargs=kwargs
- )
+ target=self.remotePath + ".snapshot", args=args, kwargs=kwargs
+ ),
)
return self.askServer(req)
@@ -119,57 +119,67 @@ class ProxyParameter(ProxyMixin, Parameter):
priority.
"""
- def __init__(self, name: str, *args,
- cli: Optional["Client"] = None,
- host: Optional[str] = 'localhost',
- port: Optional[int] = DEFAULT_PORT,
- remotePath: Optional[str] = None,
- bluePrint: Optional[ParameterBluePrint] = None,
- setpoints_instrument: Optional[Instrument] = None,
- **kwargs):
-
- super().__init__(name, *args, cli=cli, host=host, port=port,
- remotePath=remotePath, bluePrint=bluePrint,
- **kwargs)
+ def __init__(
+ self,
+ name: str,
+ *args: Any,
+ cli: Optional["Client"] = None,
+ host: Optional[str] = "localhost",
+ port: Optional[int] = DEFAULT_PORT,
+ remotePath: Optional[str] = None,
+ bluePrint: Optional[ParameterBluePrint] = None,
+ setpoints_instrument: Optional[Instrument] = None,
+ **kwargs: Any,
+ ) -> None:
+
+ super().__init__(
+ name,
+ *args,
+ cli=cli,
+ host=host,
+ port=port,
+ remotePath=remotePath,
+ bluePrint=bluePrint,
+ **kwargs,
+ )
# add setpoints to parameter if we deal with ParameterWithSetpoints
if self.bp.setpoints is not None and setpoints_instrument is not None:
- setpoints = [getattr(setpoints_instrument, setpoint) for
- setpoint in self.bp.setpoints]
- setattr(self, 'setpoints', setpoints)
-
- def initKwargsFromBluePrint(self, bp):
- kwargs = {}
+ setpoints = [
+ getattr(setpoints_instrument, setpoint)
+ for setpoint in self.bp.setpoints
+ ]
+ setattr(self, "setpoints", setpoints)
+
+ def initKwargsFromBluePrint(self, bp: Any) -> Dict[str, Any]:
+ kwargs: Dict[str, Any] = {}
if bp.settable:
- kwargs['set_cmd'] = self._remoteSet
+ kwargs["set_cmd"] = self._remoteSet
else:
- kwargs['set_cmd'] = False
+ kwargs["set_cmd"] = False
if bp.gettable:
- kwargs['get_cmd'] = self._remoteGet
+ kwargs["get_cmd"] = self._remoteGet
else:
- kwargs['get_cmd'] = False
- kwargs['unit'] = bp.unit
+ kwargs["get_cmd"] = False
+ kwargs["unit"] = bp.unit
# FIXME: uncomment after implementing serializable validators
# kwargs['vals'] = bp.vals
- kwargs['docstring'] = bp.docstring
+ kwargs["docstring"] = bp.docstring
return kwargs
- def _remoteSet(self, value: Any):
+ def _remoteSet(self, value: Any) -> Any:
msg = ServerInstruction(
operation=Operation.call,
- call_spec=CallSpec(
- target=self.remotePath,
- args=(value,)
- )
+ call_spec=CallSpec(target=self.remotePath, args=(value,)),
)
return self.askServer(msg)
- def _remoteGet(self):
+ def _remoteGet(self) -> Any:
msg = ServerInstruction(
operation=Operation.call,
call_spec=CallSpec(
target=self.remotePath,
- )
+ ),
)
return self.askServer(msg)
@@ -184,31 +194,44 @@ class ProxyInstrumentModule(ProxyMixin, InstrumentBase):
:param port: The port number of the server.
"""
- def __init__(self, name: str, *args,
- cli: Optional["Client"] = None,
- host: Optional[str] = 'localhost',
- port: Optional[int] = DEFAULT_PORT,
- remotePath: Optional[str] = None,
- bluePrint: Optional[InstrumentModuleBluePrint] = None,
- **kwargs):
-
- super().__init__(name, *args, cli=cli, host=host, port=port,
- remotePath=remotePath, bluePrint=bluePrint, **kwargs)
+ def __init__(
+ self,
+ name: str,
+ *args: Any,
+ cli: Optional["Client"] = None,
+ host: Optional[str] = "localhost",
+ port: Optional[int] = DEFAULT_PORT,
+ remotePath: Optional[str] = None,
+ bluePrint: Optional[InstrumentModuleBluePrint] = None,
+ **kwargs: Any,
+ ) -> None:
+
+ super().__init__(
+ name,
+ *args,
+ cli=cli,
+ host=host,
+ port=port,
+ remotePath=remotePath,
+ bluePrint=bluePrint,
+ **kwargs,
+ )
# FIXME: This is not consistent with how mixin handles a `None` client. However, this seems like a more
# elegant solution than any time we need the client to check to be None, start a new context client instead.
if cli is None:
- self.cli = Client(host=host, port=port)
+ self.cli = Client(host=host, port=port) # type: ignore[arg-type]
for mn in self.bp.methods.keys():
- if mn == 'remove_parameter':
- def remove_parameter(obj, name: str):
- obj.cli.call(f'{obj.remotePath}.remove_parameter', name)
+ if mn == "remove_parameter":
+
+ def remove_parameter(obj: Any, name: str) -> None:
+ obj.cli.call(f"{obj.remotePath}.remove_parameter", name)
obj.update()
- self.remove_parameter = MethodType(remove_parameter, self)
+ self.remove_parameter = MethodType(remove_parameter, self) # type: ignore[method-assign]
- self.parameters.pop('IDN', None) # we will redefine this later
+ self.parameters.pop("IDN", None) # we will redefine this later
# When a new parameter or method is added to client, qcodes checks if that item exists or not. This is done
# by calling __getattr__ method. The problem is that when that method gets called and cannot find that item it
@@ -219,7 +242,7 @@ def remove_parameter(obj, name: str):
self.update()
@contextmanager
- def _updating(self):
+ def _updating(self) -> Any:
old = self.is_updating
self.is_updating = True
try:
@@ -227,18 +250,17 @@ def _updating(self):
finally:
self.is_updating = old
-
- def initKwargsFromBluePrint(self, bp):
+ def initKwargsFromBluePrint(self, bp: Any) -> Dict[str, Any]:
return {}
- def update(self):
- self.cli.invalidateBlueprint(self.remotePath)
- self.bp = self.cli.getBluePrint(self.remotePath)
+ def update(self) -> None:
+ self.cli.invalidateBlueprint(self.remotePath) # type: ignore[union-attr]
+ self.bp = self.cli.getBluePrint(self.remotePath) # type: ignore[union-attr]
self._getProxyParameters()
self._getProxyMethods()
self._getProxySubmodules()
-
- def set_parameters(self, **param_dict:dict):
+
+ def set_parameters(self, **param_dict: Any) -> None:
"""
Set instrument parameters in batch with a dict, keyed by parameter names.
@@ -247,9 +269,11 @@ def set_parameters(self, **param_dict:dict):
try:
self.parameters[k](v)
except KeyError:
- raise KeyError(f"{self.bp.instrument_module_class} instrument does not have parameter '{k}'")
+ raise KeyError(
+ f"{self.bp.instrument_module_class} instrument does not have parameter '{k}'"
+ )
- def add_parameter(self, name: str, *arg, **kw):
+ def add_parameter(self, name: str, *arg: Any, **kw: Any) -> None: # type: ignore[override]
"""Add a parameter to the proxy instrument.
If a parameter of that name already exists in the server-side instrument,
@@ -259,25 +283,21 @@ def add_parameter(self, name: str, *arg, **kw):
"""
if name in self.parameters:
- raise ValueError(f'Parameter: {name} already present in the proxy.')
+ raise ValueError(f"Parameter: {name} already present in the proxy.")
- bp: InstrumentModuleBluePrint
if self.cli is None:
raise ValueError("No client is connected to the proxy instrument.")
- bp = self.cli.getBluePrint(self.name)
self.cli.call(self.name + ".add_parameter", name, *arg, **kw)
self.update()
- def remove_parameter(self, name: str, *arg, **kw):
+ def remove_parameter(self, name: str, *arg: Any, **kw: Any) -> None:
"""Removes parameter from the proxy instrument.
Checking whether the paremeter exists or not is left to the instrument in the server. This is to avoid having
to check on every submodule for the parameter manager.
"""
- bp: InstrumentModuleBluePrint
if self.cli is None:
raise ValueError("No client is connected to the proxy instrument.")
- bp = self.cli.getBluePrint(self.name)
self.cli.call(self.name + ".remove_parameter", name, *arg, **kw)
self.update()
@@ -294,8 +314,15 @@ def _getProxyParameters(self) -> None:
if pn not in self.parameters:
pbp = self.cli.getBluePrint(f"{self.remotePath}.{pn}")
with self._updating():
- super().add_parameter(pbp.name, ProxyParameter, cli=self.cli, host=self.host,
- port=self.port, bluePrint=pbp, setpoints_instrument=self)
+ super().add_parameter(
+ pbp.name,
+ ProxyParameter,
+ cli=self.cli,
+ host=self.host,
+ port=self.port,
+ bluePrint=pbp,
+ setpoints_instrument=self,
+ )
delKeys = []
for pn in self.parameters.keys():
@@ -306,7 +333,7 @@ def _getProxyParameters(self) -> None:
for k in delKeys:
del self.parameters[k]
- def _getProxyMethods(self):
+ def _getProxyMethods(self) -> None:
"""Based on the method blue print replied from server, add the
instrument functions to the proxy instrument class.
"""
@@ -317,11 +344,11 @@ def _getProxyMethods(self):
setattr(self, n, MethodType(fun, self))
self.functions[n] = getattr(self, n)
- def _makeProxyMethod(self, bp: MethodBluePrint):
- def wrap(*a, **k):
+ def _makeProxyMethod(self, bp: MethodBluePrint) -> Callable:
+ def wrap(*a: Any, **k: Any) -> Any:
msg = ServerInstruction(
operation=Operation.call,
- call_spec=CallSpec(target=bp.path, args=a, kwargs=k)
+ call_spec=CallSpec(target=bp.path, args=a, kwargs=k),
)
return self.askServer(msg)
@@ -331,8 +358,11 @@ def wrap(*a, **k):
# FIXME: a better solution to this would probably be to convet kind into the enum object. But it seems that the
# parameter kind enum is private.
for pn, kind in params.items():
- if kind in [str(inspect.Parameter.POSITIONAL_OR_KEYWORD), str(inspect.Parameter.POSITIONAL_ONLY)]:
- args.append(f'{pn}')
+ if kind in [
+ str(inspect.Parameter.POSITIONAL_OR_KEYWORD),
+ str(inspect.Parameter.POSITIONAL_ONLY),
+ ]:
+ args.append(f"{pn}")
elif kind == str(inspect.Parameter.VAR_POSITIONAL):
args.append(f"*{pn}")
elif kind == str(inspect.Parameter.KEYWORD_ONLY):
@@ -342,27 +372,28 @@ def wrap(*a, **k):
# we need to add a `self` argument because we want this to be a bound
# method of the instrument instance.
- sig = sig[0] + 'self, ' + sig[1:]
+ sig = sig[0] + "self, " + sig[1:]
new_func_str = f"""from typing import *\ndef {bp.name}{sig}:
- return wrap({', '.join(args)})"""
+ return wrap({", ".join(args)})"""
# make sure the method knows the wrap function.
# TODO: this is not complete!
- globs = {'wrap': wrap, 'qcodes': qc, 'collections': collections}
- _ret = exec(new_func_str, globs)
+ globs = {"wrap": wrap, "qcodes": qc, "collections": collections}
+ exec(new_func_str, globs)
fun = globs[bp.name]
fun.__doc__ = bp.docstring
- return globs[bp.name]
+ return globs[bp.name] # type: ignore[return-value]
- def _getProxySubmodules(self):
+ def _getProxySubmodules(self) -> None:
"""Based on the submodule blue print replied from server, add the proxy
submodules to the proxy module class.
"""
for sn, s in self.bp.submodules.items():
if sn not in self.submodules:
submodule = ProxyInstrumentModule(
- s.name, cli=self.cli, host=self.host, port=self.port, bluePrint=s)
- self.add_submodule(sn, submodule)
+ s.name, cli=self.cli, host=self.host, port=self.port, bluePrint=s
+ )
+ self.add_submodule(sn, submodule) # type: ignore[type-var]
else:
self.submodules[sn].update()
@@ -373,7 +404,7 @@ def _getProxySubmodules(self):
for k in delKeys:
del self.submodules[k]
- def _refreshProxySubmodules(self):
+ def _refreshProxySubmodules(self) -> None:
delKeys = []
for sn, s in self.submodules.items():
if sn in self.bp.submodules:
@@ -384,16 +415,17 @@ def _refreshProxySubmodules(self):
for sn, s in self.bp.submodules.items():
if sn not in self.submodules:
submodule = ProxyInstrumentModule(
- s.name, cli=self.cli, host=self.host, port=self.port, bluePrint=s)
- self.add_submodule(sn, submodule)
+ s.name, cli=self.cli, host=self.host, port=self.port, bluePrint=s
+ )
+ self.add_submodule(sn, submodule) # type: ignore[type-var]
else:
self.submodules[sn].update()
- def __getattr__(self, item):
+ def __getattr__(self, item: str) -> Any:
try:
return super().__getattr__(item)
except Exception as e:
- current_bp = self.cli.getBluePrint(self.remotePath)
+ current_bp = self.cli.getBluePrint(self.remotePath) # type: ignore[union-attr]
if not self.is_updating:
if item in current_bp.parameters and item not in self.parameters:
self.bp = current_bp
@@ -414,24 +446,41 @@ def __getattr__(self, item):
class Client(BaseClient):
"""Client with common server requests as convenience functions."""
- def __init__(self, host='localhost', port=DEFAULT_PORT, connect=True, timeout=20, raise_exceptions=True):
+
+ def __init__(
+ self,
+ host: str = "localhost",
+ port: int = DEFAULT_PORT,
+ connect: bool = True,
+ timeout: float = 20,
+ raise_exceptions: bool = True,
+ ) -> None:
super().__init__(host, port, connect, timeout, raise_exceptions)
- self._bp_cache = {}
+ self._bp_cache: Dict[str, Any] = {}
self._bp_cache_lock = threading.Lock()
def list_instruments(self) -> Dict[str, str]:
- """ Get the existing instruments on the server.
- """
+ """Get the existing instruments on the server."""
message = ServerInstruction(operation=Operation.get_existing_instruments)
try:
return self.ask(message)
except Exception as e:
- logger.error(f"Failed to send or receive message to server at {self.host}:{self.port}", exc_info=True)
- raise RuntimeError("Communication with server failed. See logs for details.") from e
-
- def find_or_create_instrument(self, name: str, instrument_class: Optional[str] = None,
- *args: Any, **kwargs: Any) -> ProxyInstrumentModule:
- """ Looks for an instrument in the server. If it cannot find it, create a new instrument on the server. Returns
+ logger.error(
+ f"Failed to send or receive message to server at {self.host}:{self.port}",
+ exc_info=True,
+ )
+ raise RuntimeError(
+ "Communication with server failed. See logs for details."
+ ) from e
+
+ def find_or_create_instrument(
+ self,
+ name: str,
+ instrument_class: Optional[str] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> ProxyInstrumentModule:
+ """Looks for an instrument in the server. If it cannot find it, create a new instrument on the server. Returns
a proxy for either the found or the new instrument.
:param name: Name of the new instrument.
@@ -446,43 +495,42 @@ def find_or_create_instrument(self, name: str, instrument_class: Optional[str] =
return ProxyInstrumentModule(name=name, cli=self, remotePath=name)
if instrument_class is None:
- raise ValueError('Need a class to create a new instrument.')
+ raise ValueError("Need a class to create a new instrument.")
if not isinstance(instrument_class, str):
- raise TypeError('Class name must be a string with the import path of the class. '
- 'If trying to start the parameter manager for example use "instrumentserver.params.ParameterManager" instead of '
- 'passing the class itself.')
+ raise TypeError(
+ "Class name must be a string with the import path of the class. "
+ 'If trying to start the parameter manager for example use "instrumentserver.params.ParameterManager" instead of '
+ "passing the class itself."
+ )
req = ServerInstruction(
operation=Operation.create_instrument,
create_instrument_spec=InstrumentCreationSpec(
- instrument_class=instrument_class,
- name=name,
- args=args,
- kwargs=kwargs
- )
+ instrument_class=instrument_class, name=name, args=args, kwargs=kwargs
+ ),
)
_ = self.ask(req)
return ProxyInstrumentModule(name=name, cli=self, remotePath=name)
- def close_instrument(self, instrument_name: str):
- self.call('close_and_remove_instrument', instrument_name)
+ def close_instrument(self, instrument_name: str) -> Any:
+ self.call("close_and_remove_instrument", instrument_name)
- def call(self, target, *args, **kwargs):
+ def call(self, target: str, *args: Any, **kwargs: Any) -> Any:
msg = ServerInstruction(
operation=Operation.call,
call_spec=CallSpec(
target=target,
args=args,
kwargs=kwargs,
- )
+ ),
)
return self.ask(msg)
- def get_instrument(self, name):
+ def get_instrument(self, name: str) -> ProxyInstrumentModule:
return ProxyInstrumentModule(name=name, cli=self, remotePath=name)
- def getBluePrint(self, path):
+ def getBluePrint(self, path: str) -> Any:
"""
get blueprint from server
:param path:
@@ -502,7 +550,7 @@ def getBluePrint(self, path):
self._bp_cache[path] = bp
return bp
- def invalidateBlueprint(self, path=None):
+ def invalidateBlueprint(self, path: Optional[str] = None) -> None:
"""
invalidate a parameter in the blueprint cache
:param path:
@@ -513,22 +561,29 @@ def invalidateBlueprint(self, path=None):
self._bp_cache.clear()
else:
for k in list(self._bp_cache):
- if k == path or k.startswith(path + '.'):
+ if k == path or k.startswith(path + "."):
del self._bp_cache[k]
- def get_snapshot(self, instrument: str | None = None, *args, **kwargs):
+ def get_snapshot(
+ self, instrument: str | None = None, *args: Any, **kwargs: Any
+ ) -> Any:
msg = ServerInstruction(
operation=Operation.call,
call_spec=CallSpec(
- target='snapshot' if instrument is None else f"{instrument}.snapshot",
+ target="snapshot" if instrument is None else f"{instrument}.snapshot",
args=args,
kwargs=kwargs,
- )
+ ),
)
return self.ask(msg)
- def getParamDict(self, instrument: str | None = None,
- attrs: List[str] = ['value'], *args, **kwargs):
+ def getParamDict(
+ self,
+ instrument: str | None = None,
+ attrs: List[str] = ["value"],
+ *args: Any,
+ **kwargs: Any,
+ ) -> Any:
msg = ServerInstruction(
operation=Operation.get_param_dict,
serialization_opts=ParameterSerializeSpec(
@@ -536,11 +591,17 @@ def getParamDict(self, instrument: str | None = None,
attrs=attrs,
args=args,
kwargs=kwargs,
- )
+ ),
)
return self.ask(msg)
- def paramsToFile(self, filePath: str, instruments: Optional[List[str]] = None, *args, **kwargs):
+ def paramsToFile(
+ self,
+ filePath: str,
+ instruments: Optional[List[str]] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
filePath = os.path.abspath(filePath)
folder, file = os.path.split(filePath)
@@ -550,7 +611,9 @@ def paramsToFile(self, filePath: str, instruments: Optional[List[str]] = None, *
else:
params = {}
for instrument_name in instruments:
- inst_params = self.getParamDict(instrument=instrument_name, *args, **kwargs)
+ inst_params = self.getParamDict( # type: ignore[misc]
+ instrument=instrument_name, *args, **kwargs
+ )
params.update(inst_params)
# Convert to nested format before saving,
@@ -558,20 +621,22 @@ def paramsToFile(self, filePath: str, instruments: Optional[List[str]] = None, *
params = flat_to_nested_dict(params)
if not os.path.exists(folder):
os.makedirs(folder)
- with open(filePath, 'w') as f:
+ with open(filePath, "w") as f:
json.dump(params, f, indent=2, sort_keys=True)
- def setParameters(self, parameters: Dict[str, Any]):
+ def setParameters(self, parameters: Dict[str, Any]) -> Any:
msg = ServerInstruction(
operation=Operation.set_params,
set_parameters=parameters,
)
return self.ask(msg)
- def paramsFromFile(self, filePath: str, instruments: Optional[List[str]] = None):
+ def paramsFromFile(
+ self, filePath: str, instruments: Optional[List[str]] = None
+ ) -> None:
params = None
if os.path.exists(filePath):
- with open(filePath, 'r') as f:
+ with open(filePath, "r") as f:
params = json.load(f)
# Convert to flat format before sending to server (setParameters expects flat)
if not is_flat_dict(params):
@@ -582,7 +647,10 @@ def paramsFromFile(self, filePath: str, instruments: Optional[List[str]] = None)
filtered_params = {}
for instrument_name in instruments:
for key, value in params.items():
- if key.startswith(instrument_name + '.') or key == instrument_name:
+ if (
+ key.startswith(instrument_name + ".")
+ or key == instrument_name
+ ):
filtered_params[key] = value
params = filtered_params
@@ -591,14 +659,18 @@ def paramsFromFile(self, filePath: str, instruments: Optional[List[str]] = None)
logger.warning(f"File {filePath} does not exist. No params loaded.")
def _getGuiConfig(self, instrumentName: str) -> Dict[str, Any]:
- """ Gets the GUI config for an instrument from the object. Should only be used in a detached server GUI."""
- msg = ServerInstruction(operation=Operation.get_gui_config, requested_path=instrumentName)
+ """Gets the GUI config for an instrument from the object. Should only be used in a detached server GUI."""
+ msg = ServerInstruction(
+ operation=Operation.get_gui_config, requested_path=instrumentName
+ )
return self.ask(msg)
+
class SubClient(QtCore.QObject):
"""
Specific subscription client used for real-time parameter updates.
"""
+
#: Signal(ParameterBroadcastBluePrint) --
#: emitted when the server broadcast either a new parameter or an update to an existing one.
update = QtCore.Signal(ParameterBroadcastBluePrint)
@@ -606,7 +678,12 @@ class SubClient(QtCore.QObject):
#: Signal emitted when the listener finishes (for proper cleanup)
finished = QtCore.Signal()
- def __init__(self, instruments: Optional[List[str]] = None, sub_host: str = 'localhost', sub_port: int = DEFAULT_PORT + 1):
+ def __init__(
+ self,
+ instruments: Optional[List[str]] = None,
+ sub_host: str = "localhost",
+ sub_port: int = DEFAULT_PORT + 1,
+ ):
"""
Creates a new subscription client.
@@ -623,11 +700,11 @@ def __init__(self, instruments: Optional[List[str]] = None, sub_host: str = 'loc
self.connected = False
self._stop = False
- self._ctx = None
- self._sock = None
+ self._ctx: Optional[zmq.Context] = None
+ self._sock: Optional[zmq.Socket] = None
@QtCore.Slot()
- def connect(self):
+ def connect(self) -> bool:
"""
Connects the subscription client with the broadcast
and runs an infinite loop to check for updates.
@@ -643,7 +720,7 @@ def connect(self):
# subscribe to the specified instruments
if self.instruments is None:
- self._sock.setsockopt_string(zmq.SUBSCRIBE, '')
+ self._sock.setsockopt_string(zmq.SUBSCRIBE, "")
else:
for ins in self.instruments:
self._sock.setsockopt_string(zmq.SUBSCRIBE, ins)
@@ -674,14 +751,14 @@ def connect(self):
return True
@QtCore.Slot()
- def stop(self):
+ def stop(self) -> None:
"""
Stops the listener gracefully.
"""
self._stop = True
self.connected = False
- def disconnect(self):
+ def disconnect(self) -> None:
"""
Alias for stop() for backwards compatibility.
"""
@@ -689,26 +766,45 @@ def disconnect(self):
class _QtAdapter(QtCore.QObject):
- def __init__(self, parent, *arg, **kw):
+ def __init__(self, parent: Optional[QtCore.QObject], *arg: Any, **kw: Any) -> None:
super().__init__(parent)
class QtClient(_QtAdapter, Client):
- def __init__(self, parent=None,
- host='localhost',
- port=DEFAULT_PORT,
- connect=True,
- timeout=5,
- raise_exceptions=True):
+ def __init__(
+ self,
+ parent: Optional[QtCore.QObject] = None,
+ host: str = "localhost",
+ port: int = DEFAULT_PORT,
+ connect: bool = True,
+ timeout: float = 5,
+ raise_exceptions: bool = True,
+ ) -> None:
# Calling the parents like this ensures that the arguments arrive to the parents properly.
_QtAdapter.__init__(self, parent=parent)
Client.__init__(self, host, port, connect, timeout, raise_exceptions)
+ def disconnect(self, *args: Any, **kwargs: Any) -> Any:
+ # QObject.disconnect() shadows BaseClient.disconnect() via MRO, so
+ # explicitly dispatch to BaseClient.disconnect() when called without
+ # Qt signal arguments. Preserve Qt's signal-disconnect semantics when
+ # invoked with signal arguments (e.g. disconnect(signal, slot)).
+ if not args and not kwargs:
+ return Client.disconnect(self)
+ return _QtAdapter.disconnect(self, *args, **kwargs)
+
class ClientStation:
- def __init__(self, host='localhost', port=DEFAULT_PORT, connect=True, timeout=20, raise_exceptions=True,
- config_path: str = None,
- param_path: str = None):
+ def __init__(
+ self,
+ host: str = "localhost",
+ port: int = DEFAULT_PORT,
+ connect: bool = True,
+ timeout: float = 20,
+ raise_exceptions: bool = True,
+ config_path: Optional[str] = None,
+ param_path: Optional[str] = None,
+ ) -> None:
"""
A lightweight container for managing a collection of proxy instruments on the client side.
@@ -762,6 +858,7 @@ def __init__(self, host='localhost', port=DEFAULT_PORT, connect=True, timeout=20
if config_path is not None:
# Use config.py to parse server config format
from instrumentserver.config import loadConfig
+
_, serverConfig, fullConfig, tempFile, _, _ = loadConfig(config_path)
tempFile.close() # Clean up temp file
@@ -771,56 +868,77 @@ def __init__(self, host='localhost', port=DEFAULT_PORT, connect=True, timeout=20
self._create_instruments(instrument_config)
self._config_path = config_path
- def _make_client(self, connect=True):
- cli = Client(host=self._host, port=self._port, connect=connect,
- timeout=self._timeout, raise_exceptions=self._raise_exceptions)
+ def _make_client(self, connect: bool = True) -> Client:
+ cli = Client(
+ host=self._host,
+ port=self._port,
+ connect=connect,
+ timeout=self._timeout,
+ raise_exceptions=self._raise_exceptions,
+ )
return cli
- def _create_instruments(self, instrument_dict: dict):
+ def _create_instruments(self, instrument_dict: dict) -> None:
"""
Create proxy instruments based on the parameters in instrument_dict.
Uses 'type' field from server config format.
"""
for name, conf in instrument_dict.items():
# Extract type (None if not present - will just get existing instrument)
- instrument_class = conf.get('type')
+ instrument_class = conf.get("type")
# Pass all other fields as kwargs (except server/GUI-specific fields)
- kwargs = {k: v for k, v in conf.items()
- if k not in ['type', 'initialize', 'gui']}
+ kwargs = {
+ k: v for k, v in conf.items() if k not in ["type", "initialize", "gui"]
+ }
instrument = self.client.find_or_create_instrument(
- name=name,
- instrument_class=instrument_class,
- **kwargs
+ name=name, instrument_class=instrument_class, **kwargs
)
self.instruments[name] = instrument
- def close_instrument(self, instrument_name:str):
+ def close_instrument(self, instrument_name: str) -> Any:
self.client.close_instrument(instrument_name)
+ def disconnect(self) -> None:
+ """Tear down the underlying client and release its zmq resources."""
+ if self.client is not None:
+ try:
+ self.client.disconnect()
+ except Exception:
+ pass
+ self.client = None # type: ignore[assignment]
+
@staticmethod
- def _remake_client_station_when_fail(func):
+ def _remake_client_station_when_fail(func: Callable) -> Callable:
"""
Decorator for remaking a client station object when function call fails
"""
- def wrapper(self, *args, **kwargs):
+ def wrapper(self: "ClientStation", *args: Any, **kwargs: Any) -> Any:
try:
retval = func(self, *args, **kwargs)
except Exception as e:
- logger.error(f"Error calling {func}: {e}. Trying to remake instrument client ", exc_info=True)
+ logger.error(
+ f"Error calling {func}: {e}. Trying to remake instrument client ",
+ exc_info=True,
+ )
self.client = self._make_client(connect=True)
self._create_instruments(self.full_config)
- logger.info(f"Successfully remade instrument client.")
+ logger.info("Successfully remade instrument client.")
retval = func(self, *args, **kwargs)
return retval
return wrapper
- def find_or_create_instrument(self, name: str, instrument_class: Optional[str] = None,
- *args: Any, **kwargs: Any) -> ProxyInstrumentModule:
- """ Looks for an instrument in the server. If it cannot find it, create a new instrument on the server. Returns
+ def find_or_create_instrument(
+ self,
+ name: str,
+ instrument_class: Optional[str] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> ProxyInstrumentModule:
+ """Looks for an instrument in the server. If it cannot find it, create a new instrument on the server. Returns
a proxy for either the found or the new instrument.
:param name: Name of the new instrument.
@@ -831,7 +949,9 @@ def find_or_create_instrument(self, name: str, instrument_class: Optional[str] =
:returns: A new virtual instrument.
"""
- ins = self.client.find_or_create_instrument(name, instrument_class, *args, **kwargs)
+ ins = self.client.find_or_create_instrument(
+ name, instrument_class, *args, **kwargs
+ )
self.instruments[name] = ins
return ins
@@ -839,7 +959,7 @@ def get_instrument(self, name: str) -> ProxyInstrument:
return self.instruments[name]
@_remake_client_station_when_fail
- def get_parameters(self, instruments: List[str] = None) -> Dict:
+ def get_parameters(self, instruments: Optional[List[str]] = None) -> Dict:
"""
Get all instrument parameters as a nested dictionary.
@@ -847,9 +967,9 @@ def get_parameters(self, instruments: List[str] = None) -> Dict:
:param instruments: list of instrument names. If None, all instrument parameters are returned.
:return:
"""
- inst_params = {}
+ inst_params: Dict[str, Any] = {}
if instruments is None:
- instruments = self.instruments.keys()
+ instruments = list(self.instruments.keys())
for name in instruments:
ins_paras = self.client.getParamDict(name, get=True)
ins_paras = flat_to_nested_dict(ins_paras)
@@ -858,7 +978,7 @@ def get_parameters(self, instruments: List[str] = None) -> Dict:
return inst_params
@_remake_client_station_when_fail
- def set_parameters(self, inst_params: Dict):
+ def set_parameters(self, inst_params: Dict) -> None:
"""
load instrument parameters from a nested dictionary.
@@ -876,13 +996,19 @@ def set_parameters(self, inst_params: Dict):
if k in self.instruments:
params_set[k] = inst_params[k]
else:
- logger.warning(f"Instrument {k} parameter neglected, as it doesn't belong to this station")
+ logger.warning(
+ f"Instrument {k} parameter neglected, as it doesn't belong to this station"
+ )
# the client `setParameters` function requires a flat param dict
self.client.setParameters(flatten_dict(params_set))
@_remake_client_station_when_fail
- def save_parameters(self, file_path: str = None, select_instruments:List[str] = None):
+ def save_parameters(
+ self,
+ file_path: Optional[str] = None,
+ select_instruments: Optional[List[str]] = None,
+ ) -> None:
"""
Save instrument parameters to a JSON file in nested format.
@@ -890,12 +1016,18 @@ def save_parameters(self, file_path: str = None, select_instruments:List[str] =
:param select_instruments: list of instrument names to save. If None, all instruments in this station are saved.
"""
file_path = file_path if file_path is not None else self.param_path
- instruments = select_instruments if select_instruments is not None else list(self.instruments.keys())
+ instruments = (
+ select_instruments
+ if select_instruments is not None
+ else list(self.instruments.keys())
+ )
# Delegate to client's paramsToFile
- self.client.paramsToFile(file_path, instruments=instruments)
+ self.client.paramsToFile(file_path, instruments=instruments) # type: ignore[arg-type]
@_remake_client_station_when_fail
- def load_parameters(self, file_path: str, select_instruments:List[str] = None):
+ def load_parameters(
+ self, file_path: str, select_instruments: Optional[List[str]] = None
+ ) -> None:
"""
Load instrument parameters from a JSON file.
@@ -904,9 +1036,12 @@ def load_parameters(self, file_path: str, select_instruments:List[str] = None):
"""
file_path = file_path if file_path is not None else self.param_path
# Delegate to client's paramsFromFile
- instruments = select_instruments if select_instruments is not None else list(self.instruments.keys())
+ instruments = (
+ select_instruments
+ if select_instruments is not None
+ else list(self.instruments.keys())
+ )
self.client.paramsFromFile(file_path, instruments=instruments)
- def __getitem__(self, item):
+ def __getitem__(self, item: str) -> ProxyInstrument:
return self.instruments[item]
-
diff --git a/instrumentserver/config.py b/src/instrumentserver/config.py
similarity index 67%
rename from instrumentserver/config.py
rename to src/instrumentserver/config.py
index f389e67..d177c87 100644
--- a/instrumentserver/config.py
+++ b/src/instrumentserver/config.py
@@ -3,18 +3,19 @@
to the config as defaults. If you are adding any extra fields to the config make sure to add the default values on those
variables since we parse the config using those.
"""
+
import io
import tempfile
-from typing import IO, Any
-
-import ruamel.yaml # type: ignore[import-untyped] # Known bugfix under no-fix status: https://sourceforge.net/p/ruamel-yaml/tickets/328/
from pathlib import Path
+from typing import IO
+
+import ruamel.yaml
# Centralised point of extra fields for the server with its default as value
-SERVERFIELDS = {'initialize': True}
+SERVERFIELDS = {"initialize": True}
# Extra fields for the GUI.
-GUIFIELD = {'type': 'instrumentserver.gui.instruments.GenericInstrument', 'kwargs': {}}
+GUIFIELD = {"type": "instrumentserver.gui.instruments.GenericInstrument", "kwargs": {}}
def loadConfig(configPath: str | Path) -> tuple[str, dict, dict, IO[bytes], dict, dict]:
@@ -36,7 +37,7 @@ def loadConfig(configPath: str | Path) -> tuple[str, dict, dict, IO[bytes], dict
guiConfig = {} # Individual gui config of each instrument
fullConfig = {} # serverConfig + guiConfig + any unfilled fields. Used for creating instruments from the gui
pollingRates = {} # Polling rates for each parameter
- ipAddresses = {} # Dictionary of IP Addresses to send broadcasts to:
+ ipAddresses = {} # Dictionary of IP Addresses to send broadcasts to:
# externalBroadcast: where to externally send parameter change broadcasts to, formatted like "tcp://address:port"
# listeningAddress: additional address to listen to messages received by the server, formatted like "address"
@@ -44,17 +45,19 @@ def loadConfig(configPath: str | Path) -> tuple[str, dict, dict, IO[bytes], dict
rawConfig = yaml.load(configPath)
if "instruments" not in rawConfig:
- raise AttributeError("All configurations must be inside the 'instruments' field. "
- "Try adding 'instruments:' at the top of the config file and "
- "indenting everything underneath.")
+ raise AttributeError(
+ "All configurations must be inside the 'instruments' field. "
+ "Try adding 'instruments:' at the top of the config file and "
+ "indenting everything underneath."
+ )
# Parse gui_defaults section (class-based GUI configuration)
gui_defaults = {}
- if 'gui_defaults' in rawConfig:
- gui_defaults = rawConfig.pop('gui_defaults')
+ if "gui_defaults" in rawConfig:
+ gui_defaults = rawConfig.pop("gui_defaults")
# Removing any extra fields
- for instrumentName, configDict in rawConfig['instruments'].items():
+ for instrumentName, configDict in rawConfig["instruments"].items():
serverConfig[instrumentName] = {}
for field, default in SERVERFIELDS.items():
if field in configDict:
@@ -67,49 +70,64 @@ def loadConfig(configPath: str | Path) -> tuple[str, dict, dict, IO[bytes], dict
# we don't go through the entire gui because generic is a special setting
# and we only have 2 different options for now
- if 'gui' in configDict:
- guiDict = configDict.pop('gui')
+ if "gui" in configDict:
+ guiDict = configDict.pop("gui")
if guiDict is None:
- raise AttributeError(f'"gui" field cannot be None')
- if 'type' in guiDict:
- if guiDict['type'] == 'generic' or guiDict['type'] == 'Generic':
- guiDict['type'] = GUIFIELD['type']
+ raise AttributeError('"gui" field cannot be None')
+ if "type" in guiDict:
+ if guiDict["type"] == "generic" or guiDict["type"] == "Generic":
+ guiDict["type"] = GUIFIELD["type"]
# If the user does not specify a gui, the default one will be used
else:
- guiDict['type'] = GUIFIELD['type']
+ guiDict["type"] = GUIFIELD["type"]
guiConfig[instrumentName] = guiDict
else:
guiConfig[instrumentName] = GUIFIELD
- if 'pollingRate' in configDict:
- ratesDict = configDict.pop('pollingRate')
+ if "pollingRate" in configDict:
+ ratesDict = configDict.pop("pollingRate")
# This catches the case when the pollingRate is in the config but it is empty.
if isinstance(ratesDict, dict):
- pollingRates.update({instrumentName + "." + param: rate for param, rate in ratesDict.items()})
-
- fullConfig[instrumentName] = {'gui': guiConfig[instrumentName], **configDict, **serverConfig[instrumentName]}
+ pollingRates.update(
+ {
+ instrumentName + "." + param: rate
+ for param, rate in ratesDict.items()
+ }
+ )
+
+ fullConfig[instrumentName] = {
+ "gui": guiConfig[instrumentName],
+ **configDict,
+ **serverConfig[instrumentName],
+ }
# Merge gui_defaults into guiConfig for each instrument
if gui_defaults:
for instrumentName in guiConfig.keys():
# Get instrument class name from the type field
- instrument_type = fullConfig[instrumentName].get('type', '')
- class_name = instrument_type.split('.')[-1] if instrument_type else ''
+ instrument_type = fullConfig[instrumentName].get("type", "")
+ class_name = instrument_type.split(".")[-1] if instrument_type else ""
# Initialize kwargs if not present
- if 'kwargs' not in guiConfig[instrumentName]:
- guiConfig[instrumentName]['kwargs'] = {}
+ if "kwargs" not in guiConfig[instrumentName]:
+ guiConfig[instrumentName]["kwargs"] = {}
# Merge patterns in order: __default__ → class → instance
# For each GUI config key (parameters-hide, methods-hide, etc.)
- for config_key in ['parameters-hide', 'methods-hide', 'parameters-star', 'parameters-trash',
- 'methods-star', 'methods-trash']:
+ for config_key in [
+ "parameters-hide",
+ "methods-hide",
+ "parameters-star",
+ "parameters-trash",
+ "methods-star",
+ "methods-trash",
+ ]:
merged_patterns = []
# 1. Add patterns from __default__
- if '__default__' in gui_defaults:
- default_config = gui_defaults['__default__']
+ if "__default__" in gui_defaults:
+ default_config = gui_defaults["__default__"]
if config_key in default_config:
merged_patterns.extend(default_config[config_key])
@@ -120,23 +138,25 @@ def loadConfig(configPath: str | Path) -> tuple[str, dict, dict, IO[bytes], dict
merged_patterns.extend(class_config[config_key])
# 3. Add patterns from instance-specific config
- if config_key in guiConfig[instrumentName]['kwargs']:
- merged_patterns.extend(guiConfig[instrumentName]['kwargs'][config_key])
+ if config_key in guiConfig[instrumentName]["kwargs"]:
+ merged_patterns.extend(
+ guiConfig[instrumentName]["kwargs"][config_key]
+ )
# Store merged patterns if any exist
if merged_patterns:
- guiConfig[instrumentName]['kwargs'][config_key] = merged_patterns
+ guiConfig[instrumentName]["kwargs"][config_key] = merged_patterns
# Update fullConfig with merged GUI config
- fullConfig[instrumentName]['gui'] = guiConfig[instrumentName]
+ fullConfig[instrumentName]["gui"] = guiConfig[instrumentName]
# Gets all of the broadcasting and listening addresses from the config file
- if 'networking' in rawConfig:
- addressDict = rawConfig['networking']
+ if "networking" in rawConfig:
+ addressDict = rawConfig["networking"]
if addressDict is not None:
for address in addressDict.items():
ipAddresses.update({address[0]: address[1]})
- rawConfig.pop('networking')
+ rawConfig.pop("networking")
# Creating the file like object
with io.BytesIO() as ioBytesFile:
diff --git a/instrumentserver/deployment/Dockerfile b/src/instrumentserver/deployment/Dockerfile
similarity index 100%
rename from instrumentserver/deployment/Dockerfile
rename to src/instrumentserver/deployment/Dockerfile
diff --git a/instrumentserver/deployment/README.md b/src/instrumentserver/deployment/README.md
similarity index 100%
rename from instrumentserver/deployment/README.md
rename to src/instrumentserver/deployment/README.md
diff --git a/instrumentserver/deployment/dashboard.json b/src/instrumentserver/deployment/dashboard.json
similarity index 100%
rename from instrumentserver/deployment/dashboard.json
rename to src/instrumentserver/deployment/dashboard.json
diff --git a/instrumentserver/deployment/docker-compose.yml b/src/instrumentserver/deployment/docker-compose.yml
similarity index 100%
rename from instrumentserver/deployment/docker-compose.yml
rename to src/instrumentserver/deployment/docker-compose.yml
diff --git a/instrumentserver/deployment/grafana.ini b/src/instrumentserver/deployment/grafana.ini
similarity index 100%
rename from instrumentserver/deployment/grafana.ini
rename to src/instrumentserver/deployment/grafana.ini
diff --git a/instrumentserver/deployment/provisioning/datasources/csvdatasource.yml b/src/instrumentserver/deployment/provisioning/datasources/csvdatasource.yml
similarity index 100%
rename from instrumentserver/deployment/provisioning/datasources/csvdatasource.yml
rename to src/instrumentserver/deployment/provisioning/datasources/csvdatasource.yml
diff --git a/src/instrumentserver/gui/__init__.py b/src/instrumentserver/gui/__init__.py
new file mode 100644
index 0000000..7a20358
--- /dev/null
+++ b/src/instrumentserver/gui/__init__.py
@@ -0,0 +1,59 @@
+from typing import Optional
+
+from .. import (
+ QtCore,
+ QtWidgets,
+ resource, # noqa: F401
+)
+
+
+def getStyleSheet() -> Optional[str]:
+ f = QtCore.QFile(":/style.css")
+ if f.open(
+ QtCore.QIODevice.OpenModeFlag.ReadOnly | QtCore.QIODevice.OpenModeFlag.Text
+ ): # type: ignore[call-overload]
+ style = f.readAll()
+ f.close()
+ return str(style, "utf-8")
+ return None
+
+
+def widgetDialog(w: QtWidgets.QWidget) -> QtWidgets.QDialog:
+ dg = QtWidgets.QDialog()
+ dg.setWindowTitle("instrumentserver")
+ dg.setWindowFlag(QtCore.Qt.WindowType.WindowMinimizeButtonHint)
+ dg.setWindowFlag(QtCore.Qt.WindowType.WindowMaximizeButtonHint)
+ dg.widget = w
+
+ css = getStyleSheet()
+ w.setStyleSheet(css)
+
+ lay = QtWidgets.QVBoxLayout(dg)
+ lay.addWidget(w)
+ lay.setContentsMargins(0, 0, 0, 0)
+ dg.setLayout(lay)
+
+ dg.show()
+ return dg
+
+
+def widgetMainWindow(
+ w: QtWidgets.QWidget, name: str = "instrumentserver"
+) -> QtWidgets.QMainWindow:
+ mw = QtWidgets.QMainWindow()
+ mw.setWindowTitle(name)
+ mw.setCentralWidget(w)
+
+ css = getStyleSheet()
+ w.setStyleSheet(css)
+
+ mw.show()
+ return mw
+
+
+def keepSmallHorizontally(w: QtWidgets.QWidget) -> None:
+ w.setSizePolicy(
+ QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum
+ )
+ )
diff --git a/instrumentserver/gui/base_instrument.py b/src/instrumentserver/gui/base_instrument.py
similarity index 71%
rename from instrumentserver/gui/base_instrument.py
rename to src/instrumentserver/gui/base_instrument.py
index 77bfa7c..5e76af5 100644
--- a/instrumentserver/gui/base_instrument.py
+++ b/src/instrumentserver/gui/base_instrument.py
@@ -104,7 +104,7 @@
import fnmatch
from pprint import pprint
-from typing import Optional, List, Dict
+from typing import Any, Dict, List, Optional, cast
from instrumentserver import QtCore, QtGui, QtWidgets
@@ -121,7 +121,14 @@ class ItemBase(QtGui.QStandardItem):
If this is None, it means that the item is a submodule and should only be there to store the children.
"""
- def __init__(self, name, star=False, trash=False, showDelegate=True, element=None,):
+ def __init__(
+ self,
+ name: str,
+ star: bool = False,
+ trash: bool = False,
+ showDelegate: bool = True,
+ element: Any = None,
+ ) -> None:
super().__init__()
self.name = name
@@ -139,11 +146,11 @@ class DelegateBase(QtWidgets.QStyledItemDelegate):
"""
@classmethod
- def getItem(cls, QModelIndex):
+ def getItem(cls, QModelIndex: QtCore.QModelIndex) -> QtGui.QStandardItem:
proxyModel = QModelIndex.model()
- model = proxyModel.sourceModel()
- item = model.itemFromIndex(proxyModel.mapToSource(QModelIndex))
+ model = proxyModel.sourceModel() # type: ignore[union-attr]
+ item = model.itemFromIndex(proxyModel.mapToSource(QModelIndex)) # type: ignore[union-attr]
if item.column != 0:
parent = item.parent()
row = item.row()
@@ -175,6 +182,7 @@ class InstrumentModelBase(QtGui.QStandardItemModel):
:param itemsHide: List of items that will not be loaded. If the user adds the same parameters to the model manually,
they will be shown.
"""
+
#: Signal(ItemBase)
#: Gets emitted after a new item has been added. The user is in charge of emitting it in their implementation
#: of addChildTo
@@ -184,13 +192,16 @@ class InstrumentModelBase(QtGui.QStandardItemModel):
#: Emitted when the model refreshes.
modelRefreshed = QtCore.Signal()
- def __init__(self, instrument,
- attr: str,
- itemClass: type[ItemBase] = ItemBase,
- itemsStar:Optional[List[str]] = [],
- itemsTrash: Optional[List[str]] = [],
- itemsHide: Optional[List[str]] = [],
- parent: Optional[QtCore.QObject] = None,):
+ def __init__(
+ self,
+ instrument: Any,
+ attr: str,
+ itemClass: type[ItemBase] = ItemBase,
+ itemsStar: Optional[List[str]] = [],
+ itemsTrash: Optional[List[str]] = [],
+ itemsHide: Optional[List[str]] = [],
+ parent: Optional[QtCore.QObject] = None,
+ ) -> None:
super().__init__(parent=parent)
@@ -231,7 +242,7 @@ def _matches_any_pattern(name: str, patterns: List[str]) -> bool:
return True
return False
- def loadItems(self, module=None, prefix=None):
+ def loadItems(self, module: Any = None, prefix: Optional[str] = None) -> None:
"""
The argument for either submodules or the instrument itself.
@@ -246,20 +257,22 @@ def loadItems(self, module=None, prefix=None):
# addItem only requires fullName, everything else is going to be passed as args and kwargs to the item
# constructor
if prefix is not None:
- objectName = '.'.join([prefix, objectName])
- if not self._matches_any_pattern(objectName, self.itemsHide):
- item = self.addItem(fullName=objectName, star=False, trash=False, element=obj)
- if self._matches_any_pattern(objectName, self.itemsTrash):
+ objectName = ".".join([prefix, objectName])
+ if not self._matches_any_pattern(objectName, self.itemsHide): # type: ignore[arg-type]
+ item = self.addItem(
+ fullName=objectName, star=False, trash=False, element=obj
+ )
+ if self._matches_any_pattern(objectName, self.itemsTrash): # type: ignore[arg-type]
self.onItemTrashToggle(item)
- if self._matches_any_pattern(objectName, self.itemsStar):
+ if self._matches_any_pattern(objectName, self.itemsStar): # type: ignore[arg-type]
self.onItemStarToggle(item)
for submodName, submod in module.submodules.items():
if prefix is not None:
- submodName = '.'.join([prefix, submodName])
+ submodName = ".".join([prefix, submodName])
self.loadItems(submod, submodName)
- def refreshAll(self):
+ def refreshAll(self) -> None:
"""
Removes all the rows from the model, updates the instrument and loads the model again.
"""
@@ -268,7 +281,9 @@ def refreshAll(self):
self.loadItems()
self.modelRefreshed.emit()
- def insertItemTo(self, parent, item):
+ def insertItemTo(
+ self, parent: QtGui.QStandardItem, item: QtGui.QStandardItem
+ ) -> None:
"""
This is the only function that actually inserts items into the model.
Overload for models that utilize more columns. **Don't call directly**
@@ -280,14 +295,13 @@ def insertItemTo(self, parent, item):
else:
parent.appendRow(item)
- def addItem(self, fullName, **kwargs):
+ def addItem(self, fullName: str, **kwargs: Any) -> "ItemBase":
"""
Adds an item to the model. The *args and **kwargs are whatever the specific item needs for a new item.
:param fullName: The name of the parameter
"""
- path = fullName.split('.')[:-1]
- paramName = fullName.split('.')[-1]
+ path = fullName.split(".")[:-1]
parent = self
smName = None
@@ -297,31 +311,52 @@ def addItem(self, fullName, **kwargs):
else:
smName = smName + f".{sm}"
- items = self.findItems(smName, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
+ items = self.findItems(
+ smName,
+ cast(
+ "QtCore.Qt.MatchFlags",
+ QtCore.Qt.MatchFlag.MatchExactly
+ | QtCore.Qt.MatchFlag.MatchRecursive,
+ ),
+ 0,
+ )
if len(items) == 0:
- subModItem = self.itemClass(name=smName, star=False, trash=False, showDelegate=False, element=None)
+ subModItem = self.itemClass(
+ name=smName,
+ star=False,
+ trash=False,
+ showDelegate=False,
+ element=None,
+ )
# submodules get directly added here and not in the load function, so need to have it here too.
if self.loadingItems:
- if not self._matches_any_pattern(smName, self.itemsHide):
- self.insertItemTo(parent, subModItem)
- if self._matches_any_pattern(smName, self.itemsTrash):
+ if not self._matches_any_pattern(smName, self.itemsHide): # type: ignore[arg-type]
+ self.insertItemTo(parent, subModItem) # type: ignore[arg-type]
+ if self._matches_any_pattern(smName, self.itemsTrash): # type: ignore[arg-type]
self.onItemTrashToggle(subModItem)
- if self._matches_any_pattern(smName, self.itemsStar):
+ if self._matches_any_pattern(smName, self.itemsStar): # type: ignore[arg-type]
self.onItemStarToggle(subModItem)
else:
- self.insertItemTo(parent, subModItem)
- parent = subModItem
+ self.insertItemTo(parent, subModItem) # type: ignore[arg-type]
+ parent = subModItem # type: ignore[assignment]
else:
- parent = items[0]
+ parent = items[0] # type: ignore[assignment]
newItem = self.itemClass(name=fullName, **kwargs)
- self.insertItemTo(parent, newItem)
+ self.insertItemTo(parent, newItem) # type: ignore[arg-type]
return newItem
- def removeItem(self, fullName):
- items = self.findItems(fullName, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
+ def removeItem(self, fullName: str) -> None:
+ items = self.findItems(
+ fullName,
+ cast(
+ "QtCore.Qt.MatchFlags",
+ QtCore.Qt.MatchFlag.MatchExactly | QtCore.Qt.MatchFlag.MatchRecursive,
+ ),
+ 0,
+ )
if len(items) > 0:
item = items[0]
@@ -334,7 +369,7 @@ def removeItem(self, fullName):
self.removeRow(item.row())
@QtCore.Slot(ItemBase)
- def onItemStarToggle(self, item):
+ def onItemStarToggle(self, item: "ItemBase") -> None:
assert isinstance(item, ItemBase)
if item.star:
item.star = False
@@ -342,10 +377,10 @@ def onItemStarToggle(self, item):
else:
item.star = True
item.trash = False
- item.setIcon(QtGui.QIcon(':/icons/star.svg'))
+ item.setIcon(QtGui.QIcon(":/icons/star.svg"))
@QtCore.Slot(ItemBase)
- def onItemTrashToggle(self, item):
+ def onItemTrashToggle(self, item: "ItemBase") -> None:
assert isinstance(item, ItemBase)
if item.trash:
item.trash = False
@@ -353,11 +388,10 @@ def onItemTrashToggle(self, item):
else:
item.trash = True
item.star = False
- item.setIcon(QtGui.QIcon(':/icons/trash.svg'))
+ item.setIcon(QtGui.QIcon(":/icons/trash.svg"))
class InstrumentSortFilterProxyModel(QtCore.QSortFilterProxyModel):
-
#: Signal()
#: Emitted before a filter occurs
filterIncoming = QtCore.Signal()
@@ -366,7 +400,9 @@ class InstrumentSortFilterProxyModel(QtCore.QSortFilterProxyModel):
#: Emitted after a filter has occurred.
filterFinished = QtCore.Signal()
- def __init__(self, sourceModel: InstrumentModelBase, parent: Optional[QtCore.QObject] = None):
+ def __init__(
+ self, sourceModel: InstrumentModelBase, parent: Optional[QtCore.QObject] = None
+ ):
super().__init__(parent=parent)
self.setSourceModel(sourceModel)
@@ -376,29 +412,31 @@ def __init__(self, sourceModel: InstrumentModelBase, parent: Optional[QtCore.QOb
self.star = False
self.trash = False
- self.sort(0, QtCore.Qt.DescendingOrder)
+ self.sort(0, QtCore.Qt.SortOrder.DescendingOrder)
@QtCore.Slot(int, QtCore.Qt.SortOrder)
- def onSortingIndicatorChanged(self, index, sortingOrder):
+ def onSortingIndicatorChanged(
+ self, index: int, sortingOrder: QtCore.Qt.SortOrder
+ ) -> None:
self.sort(index, sortingOrder)
@QtCore.Slot()
- def onToggleStar(self):
+ def onToggleStar(self) -> None:
if self.star:
self.star = False
else:
self.star = True
# When the start status changes, trigger a sorting so that the star items move.
- if self.sortOrder() == QtCore.Qt.DescendingOrder:
- self.sort(0, QtCore.Qt.AscendingOrder)
- self.sort(0, QtCore.Qt.DescendingOrder)
- elif self.sortOrder() == QtCore.Qt.AscendingOrder:
- self.sort(0, QtCore.Qt.DescendingOrder)
- self.sort(0, QtCore.Qt.AscendingOrder)
+ if self.sortOrder() == QtCore.Qt.SortOrder.DescendingOrder:
+ self.sort(0, QtCore.Qt.SortOrder.AscendingOrder)
+ self.sort(0, QtCore.Qt.SortOrder.DescendingOrder)
+ elif self.sortOrder() == QtCore.Qt.SortOrder.AscendingOrder:
+ self.sort(0, QtCore.Qt.SortOrder.DescendingOrder)
+ self.sort(0, QtCore.Qt.SortOrder.AscendingOrder)
@QtCore.Slot()
- def onToggleTrash(self):
+ def onToggleTrash(self) -> None:
if self.trash:
self.trash = False
else:
@@ -406,17 +444,17 @@ def onToggleTrash(self):
self.triggerFiltering()
@QtCore.Slot(str)
- def onTextFilterChange(self, filter: str):
+ def onTextFilterChange(self, filter: str) -> None:
self.filterIncoming.emit()
self.setFilterRegExp(filter)
self.filterFinished.emit()
- def triggerFiltering(self):
+ def triggerFiltering(self) -> None:
self.filterIncoming.emit()
self.invalidateFilter()
self.filterFinished.emit()
- def _isParentTrash(self, parent):
+ def _isParentTrash(self, parent: Optional["ItemBase"]) -> bool:
"""
Recursive function to see if any parent of an item is trash.
"""
@@ -426,9 +464,11 @@ def _isParentTrash(self, parent):
if parent.trash:
return True
- return self._isParentTrash(parent.parent())
+ return self._isParentTrash(parent.parent()) # type: ignore[arg-type]
- def filterAcceptsRow(self, source_row: int, source_parent: QtCore.QModelIndex) -> bool:
+ def filterAcceptsRow(
+ self, source_row: int, source_parent: QtCore.QModelIndex
+ ) -> bool:
"""
Calls for the super() unless trash is active and the item or one of its parent is trash.
"""
@@ -442,12 +482,14 @@ def filterAcceptsRow(self, source_row: int, source_parent: QtCore.QModelIndex) -
# The order in which things get constructed seems to impact this.
# When the application is first starting, the proxy model does not have the trash attribute.
- if hasattr(self, 'trash'):
+ if hasattr(self, "trash"):
if self.trash:
# Assertion is there to satisfy mypy. item can be None, that is why we check before making the assertion
if item is not None:
assert isinstance(item, ItemBase)
- if self._isParentTrash(parent) or getattr(item, "trash", False): # item could be None when it's trashed and hidden
+ if self._isParentTrash(parent) or getattr( # type: ignore[arg-type]
+ item, "trash", False
+ ): # item could be None when it's trashed and hidden
return False
return super().filterAcceptsRow(source_row, source_parent)
@@ -458,31 +500,30 @@ def lessThan(self, left: QtCore.QModelIndex, right: QtCore.QModelIndex) -> bool:
"""
# The order in which things get constructed seems to impact this.
- # When the application is first starting, the proxy model does not have the star attribute.
- if hasattr(self, 'star'):
+ # When the application is first starting, the proxy model does not have the star attribute.
+ if hasattr(self, "star"):
if self.star:
model = self.sourceModel()
assert isinstance(model, InstrumentModelBase)
leftItem = model.itemFromIndex(left)
rightItem = model.itemFromIndex(right)
- if hasattr(leftItem, 'star') and hasattr(rightItem, 'star'):
- if self.sortOrder() == QtCore.Qt.DescendingOrder:
- if rightItem.star and not leftItem.star:
+ if hasattr(leftItem, "star") and hasattr(rightItem, "star"):
+ if self.sortOrder() == QtCore.Qt.SortOrder.DescendingOrder:
+ if rightItem.star and not leftItem.star: # type: ignore[union-attr]
return True
- elif not rightItem.star and leftItem.star:
+ elif not rightItem.star and leftItem.star: # type: ignore[union-attr]
return False
- elif self.sortOrder() == QtCore.Qt.AscendingOrder:
- if rightItem.star and not leftItem.star:
+ elif self.sortOrder() == QtCore.Qt.SortOrder.AscendingOrder:
+ if rightItem.star and not leftItem.star: # type: ignore[union-attr]
return False
- elif not rightItem.star and leftItem.star:
+ elif not rightItem.star and leftItem.star: # type: ignore[union-attr]
return True
return super().lessThan(left, right)
class InstrumentTreeViewBase(QtWidgets.QTreeView):
-
#: Signal(ItemBase)
#: emitted when this item got its trashed action triggered.
itemTrashToggle = QtCore.Signal(ItemBase)
@@ -491,7 +532,12 @@ class InstrumentTreeViewBase(QtWidgets.QTreeView):
#: emitted when this item got its star action triggered.
itemStarToggle = QtCore.Signal(ItemBase)
- def __init__(self, model, delegateColumns: Optional[List[int]]=None, parent: Optional[QtWidgets.QWidget] = None):
+ def __init__(
+ self,
+ model: QtCore.QAbstractItemModel,
+ delegateColumns: Optional[List[int]] = None,
+ parent: Optional[QtWidgets.QWidget] = None,
+ ) -> None:
super().__init__(parent=parent)
# Indicates if a column is using delegates.
@@ -509,37 +555,37 @@ def __init__(self, model, delegateColumns: Optional[List[int]]=None, parent: Opt
# the real model
m = self.model()
assert isinstance(m, InstrumentSortFilterProxyModel)
- assert hasattr(m, 'sourceModel')
+ assert hasattr(m, "sourceModel")
self.modelActual = m.sourceModel()
# We need to turn sorting off so that the view sorting does not interfere with the proxy model sorting.
self.setSortingEnabled(False)
# The tree should not have anything to do with filtering itself since that is left for the proxy model.
- self.header().setSortIndicatorShown(True)
- self.header().setSectionsClickable(True)
+ self.header().setSortIndicatorShown(True) # type: ignore[union-attr]
+ self.header().setSectionsClickable(True) # type: ignore[union-attr]
self.setAlternatingRowColors(True)
- self.starIcon = QtGui.QIcon(':/icons/star.svg')
- self.starCrossedIcon = QtGui.QIcon(':/icons/star-crossed.svg')
- self.trashIcon = QtGui.QIcon(':/icons/trash.svg')
- self.trashCrossedIcon = QtGui.QIcon(':/icons/trash-crossed')
+ self.starIcon = QtGui.QIcon(":/icons/star.svg")
+ self.starCrossedIcon = QtGui.QIcon(":/icons/star-crossed.svg")
+ self.trashIcon = QtGui.QIcon(":/icons/trash.svg")
+ self.trashCrossedIcon = QtGui.QIcon(":/icons/trash-crossed")
- self.starItemAction = QtWidgets.QAction(self.starIcon, 'Star Item')
+ self.starItemAction = QtWidgets.QAction(self.starIcon, "Star Item")
self.starItemAction.triggered.connect(self.onStarActionTrigger)
- self.trashItemAction = QtWidgets.QAction(self.trashIcon, 'Trash Item')
+ self.trashItemAction = QtWidgets.QAction(self.trashIcon, "Trash Item")
self.trashItemAction.triggered.connect(self.onTrashActionTrigger)
self.contextMenu = QtWidgets.QMenu(self)
self.contextMenu.addAction(self.starItemAction)
self.contextMenu.addAction(self.trashItemAction)
- self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.customContextMenuRequested.connect(self.onContextMenuRequested)
@QtCore.Slot()
- def fillCollapsedDict(self, parentItem: Optional[ItemBase]=None):
+ def fillCollapsedDict(self, parentItem: Optional[ItemBase] = None) -> None:
"""
Fills the collapsed state dictionary to be recovered after a filter event occured.
"""
@@ -554,8 +600,8 @@ def fillCollapsedDict(self, parentItem: Optional[ItemBase]=None):
proxyIndex = m.mapFromSource(index)
if proxyIndex.isValid():
self.collapsedState[persistentIndex] = self.isExpanded(proxyIndex)
- if item.hasChildren():
- self.fillCollapsedDict(item)
+ if item.hasChildren(): # type: ignore[union-attr]
+ self.fillCollapsedDict(item) # type: ignore[arg-type]
else:
for i in range(parentItem.rowCount()):
child = parentItem.child(i, 0)
@@ -566,60 +612,83 @@ def fillCollapsedDict(self, parentItem: Optional[ItemBase]=None):
proxyIndex = m.mapFromSource(childIndex)
if proxyIndex.isValid():
self.collapsedState[persistentIndex] = self.isExpanded(proxyIndex)
- if child.hasChildren():
- self.fillCollapsedDict(child)
+ if child.hasChildren(): # type: ignore[union-attr]
+ self.fillCollapsedDict(child) # type: ignore[arg-type]
@QtCore.Slot()
- def restoreCollapsedDict(self):
+ def restoreCollapsedDict(self) -> None:
"""
Goes through the collapsed state dictionary, and expands any item that should be expanded. It also resets
the persistent editors and triggers a resizing of delegates.
"""
for persistentIndex, state in self.collapsedState.items():
- modelIndex = self.modelActual.index(persistentIndex.row(), persistentIndex.column(), persistentIndex.parent())
- item = self.modelActual.itemFromIndex(modelIndex)
- proxyIndex = self.model().mapFromSource(modelIndex)
+ modelIndex = self.modelActual.index( # type: ignore[union-attr]
+ persistentIndex.row(),
+ persistentIndex.column(),
+ persistentIndex.parent(),
+ )
+ item = self.modelActual.itemFromIndex(modelIndex) # type: ignore[union-attr]
+ proxyIndex = self.model().mapFromSource(modelIndex) # type: ignore[union-attr]
self.setExpanded(proxyIndex, state)
if item.showDelegate:
- delegateIndexes = [self.modelActual.index(persistentIndex.row(), x, persistentIndex.parent()) for x in
- self.delegateColumns]
- proxyDelegateIndexes = [self.model().mapFromSource(index) for index in delegateIndexes]
+ delegateIndexes = [
+ self.modelActual.index( # type: ignore[union-attr]
+ persistentIndex.row(), x, persistentIndex.parent()
+ )
+ for x in self.delegateColumns # type: ignore[union-attr]
+ ]
+ proxyDelegateIndexes = [
+ self.model().mapFromSource(index) # type: ignore[union-attr]
+ for index in delegateIndexes
+ ]
for delegateIndex in proxyDelegateIndexes:
self.openPersistentEditor(delegateIndex)
self.scheduleDelayedItemsLayout()
- def setAllDelegatesPersistent(self, parentIndex=None):
+ def setAllDelegatesPersistent(
+ self, parentIndex: Optional[QtCore.QModelIndex] = None
+ ) -> None:
"""
Recursive function that goes through the entire model and sets all delegates to be persistent editors
:param parentIndex: If None, start the process. if it's an item, it will go through the children
"""
if parentIndex is None:
- for i in range(self.model().rowCount()):
- for column in self.delegateColumns:
- index = self.model().index(i, column)
- index0 = self.model().index(i, 0) # Only items at column 0 hold children and model info
- item0 = self.modelActual.itemFromIndex(self.model().mapToSource(index0))
+ for i in range(self.model().rowCount()): # type: ignore[union-attr]
+ for column in self.delegateColumns: # type: ignore[union-attr]
+ index = self.model().index(i, column) # type: ignore[union-attr]
+ index0 = self.model().index( # type: ignore[union-attr]
+ i, 0
+ ) # Only items at column 0 hold children and model info
+ item0 = self.modelActual.itemFromIndex( # type: ignore[union-attr]
+ self.model().mapToSource(index0) # type: ignore[union-attr]
+ )
if item0.showDelegate:
self.openPersistentEditor(index)
if item0.hasChildren():
self.setAllDelegatesPersistent(index0)
else:
- parentItem = self.modelActual.itemFromIndex(self.model().mapToSource(parentIndex))
+ parentItem = self.modelActual.itemFromIndex( # type: ignore[union-attr]
+ self.model().mapToSource(parentIndex) # type: ignore[union-attr]
+ )
for i in range(parentItem.rowCount()):
- for column in self.delegateColumns:
+ for column in self.delegateColumns: # type: ignore[union-attr]
item = parentItem.child(i, column)
item0 = parentItem.child(i, 0)
- index = self.model().mapFromSource(self.modelActual.indexFromItem(item))
- index0 = self.model().mapFromSource(self.modelActual.indexFromItem(item0))
+ index = self.model().mapFromSource( # type: ignore[union-attr]
+ self.modelActual.indexFromItem(item) # type: ignore[union-attr]
+ )
+ index0 = self.model().mapFromSource( # type: ignore[union-attr]
+ self.modelActual.indexFromItem(item0) # type: ignore[union-attr]
+ )
if item0.showDelegate:
self.openPersistentEditor(index)
if item0.hasChildren():
self.setAllDelegatesPersistent(index0)
@QtCore.Slot(object)
- def onCheckDelegate(self, item):
+ def onCheckDelegate(self, item: Optional["ItemBase"]) -> None:
"""
Makes sure that the delegates are shown if needed.
@@ -629,22 +698,24 @@ def onCheckDelegate(self, item):
if item.showDelegate:
row = item.row()
parent = item.parent()
- for column in self.delegateColumns:
+ for column in self.delegateColumns: # type: ignore[union-attr]
if parent is None:
- sibling = self.modelActual.item(row, column)
+ sibling = self.modelActual.item(row, column) # type: ignore[union-attr]
else:
sibling = parent.child(row, column)
- index = self.model().mapFromSource(self.modelActual.indexFromItem(sibling))
+ index = self.model().mapFromSource( # type: ignore[union-attr]
+ self.modelActual.indexFromItem(sibling) # type: ignore[union-attr]
+ )
self.openPersistentEditor(index)
self.scheduleDelayedItemsLayout()
@QtCore.Slot(QtCore.QPoint)
- def onContextMenuRequested(self, pos):
+ def onContextMenuRequested(self, pos: QtCore.QPoint) -> None:
# We get the item from the real model, not the proxy model
- originalModel = self.model().sourceModel()
+ originalModel = self.model().sourceModel() # type: ignore[union-attr]
proxyIndex = self.indexAt(pos)
- index = self.model().mapToSource(proxyIndex)
+ index = self.model().mapToSource(proxyIndex) # type: ignore[union-attr]
# catch the case if the user rightcliks on any other column
if index.column() != 0:
@@ -661,27 +732,27 @@ def onContextMenuRequested(self, pos):
self.lastSelectedItem = item
if item.star:
- self.starItemAction.setText('un-star item')
+ self.starItemAction.setText("un-star item")
self.starItemAction.setIcon(self.starCrossedIcon)
else:
- self.starItemAction.setText('star item')
+ self.starItemAction.setText("star item")
self.starItemAction.setIcon(self.starIcon)
if item.trash:
- self.trashItemAction.setText('un-trash item')
+ self.trashItemAction.setText("un-trash item")
self.trashItemAction.setIcon(self.trashCrossedIcon)
else:
- self.trashItemAction.setText('trash item')
+ self.trashItemAction.setText("trash item")
self.trashItemAction.setIcon(self.trashIcon)
self.contextMenu.exec_(self.mapToGlobal(pos))
@QtCore.Slot()
- def onStarActionTrigger(self):
+ def onStarActionTrigger(self) -> None:
self.itemStarToggle.emit(self.lastSelectedItem)
@QtCore.Slot()
- def onTrashActionTrigger(self):
+ def onTrashActionTrigger(self) -> None:
self.itemTrashToggle.emit(self.lastSelectedItem)
@@ -690,24 +761,28 @@ class InstrumentDisplayBase(QtWidgets.QWidget):
Basic widget. To implement new toolbars overload the makeToolBar function. To connect any extra signals overload the connectSignals function.
All the type variables, require the class type and not an initialized object of the variables.
-
+
:param instrument: The instrument we want to display the attribute from.
:param attr: string of the name of the dictionary we want to display, like 'parameters' or 'function'
- :param itemType: The type of item the model should use.
+ :param itemType: The type of item the model should use.
:param modelType: The type of model that should be used.
:param proxyModelType: The type of proxy model that should be used.
:param viewType: The type of view that should be used.
:param callSignals: If False, the constructor will not call the method connectSignals
"""
- def __init__(self, instrument,
- attr: str,
- itemType = ItemBase,
- modelType = InstrumentModelBase,
- proxyModelType = InstrumentSortFilterProxyModel,
- viewType = InstrumentTreeViewBase,
- callSignals: bool = True,
- parent: Optional[QtWidgets.QWidget] = None,
- **modelKwargs):
+
+ def __init__(
+ self,
+ instrument: Any,
+ attr: str,
+ itemType: type = ItemBase,
+ modelType: type = InstrumentModelBase,
+ proxyModelType: type = InstrumentSortFilterProxyModel,
+ viewType: type = InstrumentTreeViewBase,
+ callSignals: bool = True,
+ parent: Optional[QtWidgets.QWidget] = None,
+ **modelKwargs: Any,
+ ) -> None:
super().__init__(parent=parent)
# initializing variables
@@ -736,7 +811,7 @@ def __init__(self, instrument,
if callSignals:
self.connectSignals()
- def connectSignals(self):
+ def connectSignals(self) -> None:
"""
Connects all the signals to slots of different classes. Override to add more signals
"""
@@ -751,9 +826,11 @@ def connectSignals(self):
self.lineEdit.textChanged.connect(self.proxyModel.onTextFilterChange)
- self.view.header().sortIndicatorChanged.connect(self.proxyModel.onSortingIndicatorChanged)
+ self.view.header().sortIndicatorChanged.connect(
+ self.proxyModel.onSortingIndicatorChanged
+ )
- def makeToolbar(self):
+ def makeToolbar(self) -> QtWidgets.QToolBar:
"""
Creates the toolbar, override to add more buttons to the toolbar.
"""
@@ -764,7 +841,7 @@ def makeToolbar(self):
QtGui.QIcon(":/icons/refresh.svg"),
"refresh all items from the instrument",
)
- refreshAction.triggered.connect(lambda x: self.refreshAll())
+ refreshAction.triggered.connect(lambda x: self.refreshAll()) # type: ignore[union-attr]
toolbar.addSeparator()
@@ -772,29 +849,27 @@ def makeToolbar(self):
QtGui.QIcon(":/icons/expand.svg"),
"expand tree",
)
- expandAction.triggered.connect(lambda x: self.view.expandAll())
+ expandAction.triggered.connect(lambda x: self.view.expandAll()) # type: ignore[union-attr]
collapseAction = toolbar.addAction(
QtGui.QIcon(":/icons/collapse.svg"),
"collapse tree",
)
- collapseAction.triggered.connect(lambda x: self.view.collapseAll())
+ collapseAction.triggered.connect(lambda x: self.view.collapseAll()) # type: ignore[union-attr]
toolbar.addSeparator()
starAction = toolbar.addAction(
- QtGui.QIcon(':/icons/star.svg'),
- "Move Starred items to the top"
+ QtGui.QIcon(":/icons/star.svg"), "Move Starred items to the top"
)
- starAction.setCheckable(True)
- starAction.triggered.connect(lambda x: self.promoteStar())
+ starAction.setCheckable(True) # type: ignore[union-attr]
+ starAction.triggered.connect(lambda x: self.promoteStar()) # type: ignore[union-attr]
trashAction = toolbar.addAction(
- QtGui.QIcon(":/icons/trash-crossed.svg"),
- "Hide trashed items"
+ QtGui.QIcon(":/icons/trash-crossed.svg"), "Hide trashed items"
)
- trashAction.setCheckable(True)
- trashAction.triggered.connect(lambda x: self.hideTrash())
+ trashAction.setCheckable(True) # type: ignore[union-attr]
+ trashAction.triggered.connect(lambda x: self.hideTrash()) # type: ignore[union-attr]
# Debugging tools keep commented for commits.
# printAction = toolbar.addAction(
@@ -808,36 +883,39 @@ def makeToolbar(self):
return toolbar
@QtCore.Slot()
- def hideTrash(self):
+ def hideTrash(self) -> None:
self.proxyModel.onToggleTrash()
@QtCore.Slot()
- def promoteStar(self):
+ def promoteStar(self) -> None:
self.proxyModel.onToggleStar()
@QtCore.Slot()
- def refreshAll(self):
+ def refreshAll(self) -> None:
self.model.refreshAll()
- def debuggingMethod(self):
+ def debuggingMethod(self) -> None:
"""
This is just a debugging method.
"""
- items = {}
+ items: Dict[str, Any] = {}
- def fillChildren(parent):
+ def fillChildren(parent: QtGui.QStandardItem) -> None:
for i in range(parent.rowCount()):
item = parent.child(i, 0)
- items[item.name] = {'item': item, 'star': item.star, 'trash': item.trash}
- if item.hasChildren():
- fillChildren(item)
+ items[item.name] = { # type: ignore[union-attr]
+ "item": item,
+ "star": item.star, # type: ignore[union-attr]
+ "trash": item.trash, # type: ignore[union-attr]
+ }
+ if item.hasChildren(): # type: ignore[union-attr]
+ fillChildren(item) # type: ignore[arg-type]
for i in range(self.model.rowCount()):
item = self.model.item(i, 0)
- items[item.name] = {'item': item, 'star': item.star, 'trash': item.trash}
+ items[item.name] = {"item": item, "star": item.star, "trash": item.trash}
if item.hasChildren():
fillChildren(item)
pprint(items)
print("\n \n \n \n")
-
diff --git a/instrumentserver/gui/instruments.py b/src/instrumentserver/gui/instruments.py
similarity index 58%
rename from instrumentserver/gui/instruments.py
rename to src/instrumentserver/gui/instruments.py
index 3834e52..1a41ae0 100644
--- a/instrumentserver/gui/instruments.py
+++ b/src/instrumentserver/gui/instruments.py
@@ -1,22 +1,25 @@
-import json
-import logging
import inspect
-from pprint import pprint
-from typing import Optional, Any, List, Tuple, Union, Callable, Dict, Type
+import logging
+from typing import Any, Callable, Dict, Optional, Union, cast
+
+from qcodes import Instrument
from instrumentserver.gui.misc import AlertLabelGreen
-from qcodes import Parameter, Instrument
-from . import parameters, keepSmallHorizontally
-from .base_instrument import InstrumentDisplayBase, ItemBase, InstrumentModelBase, InstrumentTreeViewBase, DelegateBase
-from .parameters import ParameterWidget, AnyInput, AnyInputForMethod
-from .. import QtWidgets, QtCore, QtGui, DEFAULT_PORT
+from .. import DEFAULT_PORT, QtCore, QtGui, QtWidgets
from ..blueprints import ParameterBroadcastBluePrint
from ..client import ProxyInstrument, SubClient
-from ..helpers import stringToArgsAndKwargs, nestedAttributeFromString
-from ..params import ParameterManager, paramTypeFromName, ParameterTypes, parameterTypes
-from ..serialize import toParamDict
-from ast import literal_eval
+from ..helpers import nestedAttributeFromString
+from ..params import ParameterManager, ParameterTypes, parameterTypes, paramTypeFromName
+from . import keepSmallHorizontally
+from .base_instrument import (
+ DelegateBase,
+ InstrumentDisplayBase,
+ InstrumentModelBase,
+ InstrumentTreeViewBase,
+ ItemBase,
+)
+from .parameters import AnyInputForMethod, ParameterWidget
# TODO: all styles set through a global style sheet.
# TODO: [maybe] add a column for information on valid input values?
@@ -38,8 +41,9 @@ class AddParameterWidget(QtWidgets.QWidget):
#: Signal(str)
invalidParamRequested = QtCore.Signal(str)
- def __init__(self, parent: Optional[QtWidgets.QWidget] = None,
- typeInput: bool = False):
+ def __init__(
+ self, parent: Optional[QtWidgets.QWidget] = None, typeInput: bool = False
+ ) -> None:
super().__init__(parent)
self.typeInput = typeInput
@@ -49,19 +53,37 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None,
self.nameEdit = QtWidgets.QLineEdit(self)
lbl = QtWidgets.QLabel("Name:")
- lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ lbl.setAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignRight
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
+ )
+ )
layout.addWidget(lbl, 0, 0)
layout.addWidget(self.nameEdit, 0, 1)
self.valueEdit = QtWidgets.QLineEdit(self)
lbl = QtWidgets.QLabel("Value:")
- lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ lbl.setAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignRight
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
+ )
+ )
layout.addWidget(lbl, 0, 2)
layout.addWidget(self.valueEdit, 0, 3)
self.unitEdit = QtWidgets.QLineEdit(self)
lbl = QtWidgets.QLabel("Unit:")
- lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ lbl.setAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignRight
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
+ )
+ )
layout.addWidget(lbl, 0, 4)
layout.addWidget(self.unitEdit, 0, 5)
@@ -69,31 +91,46 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None,
self.typeSelect = QtWidgets.QComboBox(self)
names: list[str] = []
for t, v in parameterTypes.items():
- names.append(str(v['name']))
+ names.append(str(v["name"]))
for n in sorted(names):
self.typeSelect.addItem(n)
- self.typeSelect.setCurrentText(str(parameterTypes[ParameterTypes.numeric]['name']))
+ self.typeSelect.setCurrentText(
+ str(parameterTypes[ParameterTypes.numeric]["name"])
+ )
lbl = QtWidgets.QLabel("Type:")
- lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ lbl.setAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignRight
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
+ )
+ )
layout.addWidget(lbl, 1, 0)
layout.addWidget(self.typeSelect, 1, 1)
self.valsArgsEdit = QtWidgets.QLineEdit(self)
- lbl = QtWidgets.QLabel('Type opts.:')
- lbl.setToolTip("Optional, for constraining parameter values."
- "Allowed args and defaults:\n"
- " - 'Numeric': min_value=-1e18, max_value=1e18\n"
- " - 'Integer': min_value=-inf, max_value=inf\n"
- " - 'String': min_length=0, max_length=1e9\n"
- 'See qcodes.utils.validators for details.')
- lbl.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
+ lbl = QtWidgets.QLabel("Type opts.:")
+ lbl.setToolTip(
+ "Optional, for constraining parameter values."
+ "Allowed args and defaults:\n"
+ " - 'Numeric': min_value=-1e18, max_value=1e18\n"
+ " - 'Integer': min_value=-inf, max_value=inf\n"
+ " - 'String': min_length=0, max_length=1e9\n"
+ "See qcodes.utils.validators for details."
+ )
+ lbl.setAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignRight
+ | QtCore.Qt.AlignmentFlag.AlignVCenter,
+ )
+ )
layout.addWidget(lbl, 1, 2)
layout.addWidget(self.valsArgsEdit, 1, 3)
self.addButton = QtWidgets.QPushButton(
- QtGui.QIcon(":/icons/plus-square.svg"),
- ' Add',
- parent=self)
+ QtGui.QIcon(":/icons/plus-square.svg"), " Add", parent=self
+ )
self.addButton.clicked.connect(self.requestNewParameter)
self.nameEdit.returnPressed.connect(self.addButton.click)
@@ -103,9 +140,8 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None,
self.addButton.setAutoDefault(True)
self.clearButton = QtWidgets.QPushButton(
- QtGui.QIcon(":/icons/delete.svg"),
- ' Clear',
- parent=self)
+ QtGui.QIcon(":/icons/delete.svg"), " Clear", parent=self
+ )
self.clearButton.setAutoDefault(True)
self.clearButton.clicked.connect(self.clear)
@@ -115,17 +151,19 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None,
self.invalidParamRequested.connect(self.setError)
@QtCore.Slot()
- def clear(self):
+ def clear(self) -> None:
self.clearError()
- self.nameEdit.setText('')
- self.valueEdit.setText('')
- self.unitEdit.setText('')
+ self.nameEdit.setText("")
+ self.valueEdit.setText("")
+ self.unitEdit.setText("")
if self.typeInput:
- self.typeSelect.setCurrentText(parameterTypes[ParameterTypes.numeric]['name'])
- self.valsArgsEdit.setText('')
+ self.typeSelect.setCurrentText(
+ parameterTypes[ParameterTypes.numeric]["name"] # type: ignore[arg-type]
+ )
+ self.valsArgsEdit.setText("")
@QtCore.Slot(bool)
- def requestNewParameter(self, _):
+ def requestNewParameter(self, _: bool) -> None:
self.clearError()
name = self.nameEdit.text().strip()
@@ -135,23 +173,23 @@ def requestNewParameter(self, _):
value = self.valueEdit.text()
unit = self.unitEdit.text()
- if hasattr(self, 'typeSelect'):
+ if hasattr(self, "typeSelect"):
ptype = paramTypeFromName(self.typeSelect.currentText())
valsArgs = self.valsArgsEdit.text()
else:
ptype = ParameterTypes.any
- valsArgs = ''
+ valsArgs = ""
self.newParamRequested.emit(name, value, unit, ptype, valsArgs)
@QtCore.Slot(str)
- def setError(self, message: str):
+ def setError(self, message: str) -> None:
self.addButton.setStyleSheet("""
QPushButton { background-color: red }
""")
self.addButton.setToolTip(message)
- def clearError(self):
+ def clearError(self) -> None:
self.addButton.setStyleSheet("")
self.addButton.setToolTip("")
@@ -165,7 +203,13 @@ class MethodDisplay(QtWidgets.QWidget):
#: emitted when the widget runs a function and is successful. Emits the return value as a string.
runSuccessful = QtCore.Signal(str)
- def __init__(self, fun, fullName=None, *args, **kwargs):
+ def __init__(
+ self,
+ fun: Callable,
+ fullName: Optional[str] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(*args, **kwargs)
self.fun = fun
@@ -194,13 +238,13 @@ def __init__(self, fun, fullName=None, *args, **kwargs):
self._layout.setContentsMargins(1, 1, 1, 1)
@QtCore.Slot()
- def runFun(self):
+ def runFun(self) -> None:
try:
args, kwargs = self.anyInput.value()
if kwargs is not None:
ret = self.fun(*args, **kwargs)
else:
- if isinstance(args, list) or isinstance(args, tuple) or args != '':
+ if isinstance(args, list) or isinstance(args, tuple) or args != "":
ret = self.fun(*args)
else:
ret = self.fun()
@@ -212,19 +256,20 @@ def runFun(self):
logger.warning(f"'{self.fullName}' Raised the following execution: {e}")
@classmethod
- def getTooltipFromFun(cls, fun: Callable):
+ def getTooltipFromFun(cls, fun: Callable) -> str:
"""
Returns the signature of the function with its documentation underneath.
"""
sig = inspect.signature(fun)
doc = inspect.getdoc(fun)
- return str(sig) + '\n\n' + str(doc)
+ return str(sig) + "\n\n" + str(doc)
# ----------------- Parameters Display Classes - Beginning -----------------------------
+
class ItemParameters(ItemBase):
- def __init__(self, unit='', **kwargs):
+ def __init__(self, unit: str = "", **kwargs: Any) -> None:
super().__init__(**kwargs)
self.unit = unit
@@ -235,23 +280,27 @@ class ParameterDelegate(DelegateBase):
The delegate for the InstrumentParameters widget.
"""
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtCore.QObject] = None) -> None:
super().__init__(parent=parent)
# Stores as key the name of the item and as value the widget that the delegate creates.
# used to keep a reference to the widget.
self.parameters: Dict[str, QtWidgets.QWidget] = {}
- def createEditor(self, widget: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,
- index: QtCore.QModelIndex) -> QtWidgets.QWidget:
+ def createEditor( # type: ignore[override]
+ self,
+ widget: QtWidgets.QWidget,
+ option: QtWidgets.QStyleOptionViewItem,
+ index: QtCore.QModelIndex,
+ ) -> QtWidgets.QWidget:
"""
This is the function that is supposed to create the widget. It should return it.
"""
item = self.getItem(index)
- element = item.element
+ element = item.element # type: ignore[attr-defined]
ret = ParameterWidget(element, widget)
- self.parameters[item.name] = ret
+ self.parameters[item.name] = ret # type: ignore[attr-defined]
# Try to fetch and display current value immediately
# ---- Chao: removed because the constructor of ParameterWidget object already calls parameter get ----
# if element.gettable:
@@ -268,58 +317,89 @@ class ModelParameters(InstrumentModelBase):
# name, second object is its new value
itemNewValue = QtCore.Signal(object, object)
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
# make sure we pass the server ip and port properly to the subscriber when the values are not defaults.
- subClientArgs = {"sub_host": kwargs.pop("sub_host", 'localhost'),
- "sub_port": kwargs.pop("sub_port", DEFAULT_PORT + 1)}
+ subClientArgs = {
+ "sub_host": kwargs.pop("sub_host", "localhost"),
+ "sub_port": kwargs.pop("sub_port", DEFAULT_PORT + 1),
+ }
super().__init__(*args, **kwargs)
self.setColumnCount(3)
- self.setHorizontalHeaderLabels([self.attr, 'unit', ''])
+ self.setHorizontalHeaderLabels([self.attr, "unit", ""])
# Live updates items
self.cliThread = QtCore.QThread()
self.subClient = SubClient([self.instrument.name], **subClientArgs)
self.subClient.moveToThread(self.cliThread)
- self.cliThread.started.connect(self.subClient.connect)
+ self.cliThread.started.connect(self.subClient.connect) # type: ignore[arg-type]
self.subClient.update.connect(self.updateParameter)
+ self.subClient.finished.connect(self.cliThread.quit)
self.cliThread.start()
+ def stopListener(self) -> None:
+ """Stop the background listener thread and wait for it to exit."""
+ if self.subClient is not None:
+ self.subClient.stop()
+ if self.cliThread is not None:
+ self.cliThread.quit()
+ self.cliThread.wait(3000)
+
@QtCore.Slot(ParameterBroadcastBluePrint)
- def updateParameter(self, bp: ParameterBroadcastBluePrint):
- fullName = '.'.join(bp.name.split('.')[1:])
+ def updateParameter(self, bp: ParameterBroadcastBluePrint) -> None:
+ fullName = ".".join(bp.name.split(".")[1:])
- if bp.action == 'parameter-creation':
+ if bp.action == "parameter-creation":
if fullName not in self.instrument.list():
self.instrument.update()
if fullName in self.instrument.list():
- self.addItem(fullName, element=nestedAttributeFromString(self.instrument, fullName))
+ self.addItem(
+ fullName,
+ element=nestedAttributeFromString(self.instrument, fullName),
+ )
- elif bp.action == 'parameter-deletion':
+ elif bp.action == "parameter-deletion":
self.removeItem(fullName)
- elif bp.action == 'parameter-update' or bp.action == 'parameter-call':
- item = self.findItems(fullName, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
+ elif bp.action == "parameter-update" or bp.action == "parameter-call":
+ item = self.findItems(
+ fullName,
+ cast(
+ "QtCore.Qt.MatchFlags",
+ QtCore.Qt.MatchFlag.MatchExactly
+ | QtCore.Qt.MatchFlag.MatchRecursive,
+ ),
+ 0,
+ )
if len(item) == 0:
- if fullName not in self.itemsHide:
+ if fullName not in self.itemsHide: # type: ignore[operator]
try:
- self.addItem(fullName, element=nestedAttributeFromString(self.instrument, fullName))
+ self.addItem(
+ fullName,
+ element=nestedAttributeFromString(
+ self.instrument, fullName
+ ),
+ )
except AttributeError:
# Parameter/submodule no longer exists (likely due to profile switch)
- logger.debug(f"Ignoring broadcast for non-existent parameter: {fullName}")
+ logger.debug(
+ f"Ignoring broadcast for non-existent parameter: {fullName}"
+ )
else:
assert isinstance(item[0], ItemBase)
# The model can't actually modify the widget since it knows nothing about the view itself.
self.itemNewValue.emit(item[0].name, bp.value)
- def insertItemTo(self, parent: QtGui.QStandardItem, item):
+ def insertItemTo(
+ self, parent: QtGui.QStandardItem, item: QtGui.QStandardItem
+ ) -> None:
if item is not None:
# A parameter might not have a unit
- unit = ''
- if item.element is not None:
- unit = item.element.unit
+ unit = ""
+ if item.element is not None: # type: ignore[attr-defined]
+ unit = item.element.unit # type: ignore[attr-defined]
unitItem = QtGui.QStandardItem(unit)
extraItem = QtGui.QStandardItem()
@@ -335,7 +415,12 @@ def insertItemTo(self, parent: QtGui.QStandardItem, item):
class ParametersTreeView(InstrumentTreeViewBase):
- def __init__(self, model, *args, **kwargs):
+ def __init__(
+ self,
+ model: QtCore.QAbstractItemModel,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(model, [2], *args, **kwargs)
self.delegate = ParameterDelegate(self)
@@ -344,43 +429,54 @@ def __init__(self, model, *args, **kwargs):
self.setAllDelegatesPersistent()
@QtCore.Slot(object, object)
- def onItemNewValue(self, itemName, value):
+ def onItemNewValue(self, itemName: str, value: Any) -> None:
widget = self.delegate.parameters[itemName]
try:
# use the abstract set method defined in parameter widget so it works for different types of widgets
widget._setMethod(value)
- except RuntimeError as e:
- logger.debug(f"Could not set value for {itemName} to {value}. Object is not being shown right now.")
+ except RuntimeError:
+ logger.debug(
+ f"Could not set value for {itemName} to {value}. Object is not being shown right now."
+ )
class InstrumentParameters(InstrumentDisplayBase):
- def __init__(self, instrument, parent=None, viewType=ParametersTreeView, callSignals: bool = True, **kwargs):
- if 'instrument' in kwargs:
- del kwargs['instrument']
+ def __init__(
+ self,
+ instrument: Any,
+ parent: Optional[QtWidgets.QWidget] = None,
+ viewType: type = ParametersTreeView,
+ callSignals: bool = True,
+ **kwargs: Any,
+ ) -> None:
+ if "instrument" in kwargs:
+ del kwargs["instrument"]
modelKwargs = {}
- if 'parameters-star' in kwargs:
- modelKwargs['itemsStar'] = kwargs.pop('parameters-star')
- if 'parameters-trash' in kwargs:
- modelKwargs['itemsTrash'] = kwargs.pop('parameters-trash')
- if 'parameters-hide' in kwargs:
- modelKwargs['itemsHide'] = kwargs.pop('parameters-hide')
+ if "parameters-star" in kwargs:
+ modelKwargs["itemsStar"] = kwargs.pop("parameters-star")
+ if "parameters-trash" in kwargs:
+ modelKwargs["itemsTrash"] = kwargs.pop("parameters-trash")
+ if "parameters-hide" in kwargs:
+ modelKwargs["itemsHide"] = kwargs.pop("parameters-hide")
# parameters for realtime update subscriber
- if 'sub_host' in kwargs:
- modelKwargs['sub_host'] = kwargs.pop('sub_host')
- if 'sub_port' in kwargs:
- modelKwargs['sub_port'] = kwargs.pop('sub_port')
-
- super().__init__(instrument=instrument,
- parent=parent,
- attr='parameters',
- itemType=ItemParameters,
- modelType=ModelParameters,
- viewType=viewType,
- callSignals=callSignals,
- **modelKwargs)
-
- def connectSignals(self):
+ if "sub_host" in kwargs:
+ modelKwargs["sub_host"] = kwargs.pop("sub_host")
+ if "sub_port" in kwargs:
+ modelKwargs["sub_port"] = kwargs.pop("sub_port")
+
+ super().__init__(
+ instrument=instrument,
+ parent=parent,
+ attr="parameters",
+ itemType=ItemParameters,
+ modelType=ModelParameters,
+ viewType=viewType,
+ callSignals=callSignals,
+ **modelKwargs,
+ )
+
+ def connectSignals(self) -> None:
super().connectSignals()
self.model.itemNewValue.connect(self.view.onItemNewValue)
@@ -395,20 +491,25 @@ class ParameterDeleteDelegate(ParameterDelegate):
#: Emits the name of the parameter to be deleted when the user presses the delete button.
removeParameter = QtCore.Signal(str)
- def createEditor(self, widget: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,
- index: QtCore.QModelIndex) -> QtWidgets.QWidget:
+ def createEditor( # type: ignore[override]
+ self,
+ widget: QtWidgets.QWidget,
+ option: QtWidgets.QStyleOptionViewItem,
+ index: QtCore.QModelIndex,
+ ) -> QtWidgets.QWidget:
item = self.getItem(index)
- element = item.element
- rw = self.makeRemoveWidget(item.name, widget)
+ element = item.element # type: ignore[attr-defined]
+ rw = self.makeRemoveWidget(item.name, widget) # type: ignore[attr-defined]
ret = ParameterWidget(parameter=element, parent=widget, additionalWidgets=[rw])
- self.parameters[item.name] = ret
+ self.parameters[item.name] = ret # type: ignore[attr-defined]
return ret
- def makeRemoveWidget(self, fullName: str, widget: QtWidgets.QWidget):
- w = QtWidgets.QPushButton(
- QtGui.QIcon(":/icons/delete.svg"), "", parent=widget)
+ def makeRemoveWidget(
+ self, fullName: str, widget: QtWidgets.QWidget
+ ) -> QtWidgets.QPushButton:
+ w = QtWidgets.QPushButton(QtGui.QIcon(":/icons/delete.svg"), "", parent=widget)
w.setStyleSheet("""
QPushButton { background-color: salmon }
""")
@@ -421,7 +522,12 @@ def makeRemoveWidget(self, fullName: str, widget: QtWidgets.QWidget):
# TODO: Make sure that the refresh button refreshes the profiles as well as the model
class ParameterManagerTreeView(InstrumentTreeViewBase):
- def __init__(self, model, *args, **kwargs):
+ def __init__(
+ self,
+ model: QtCore.QAbstractItemModel,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(model, [2], *args, **kwargs)
self.delegate = ParameterDeleteDelegate(self)
@@ -430,22 +536,21 @@ def __init__(self, model, *args, **kwargs):
self.setAllDelegatesPersistent()
@QtCore.Slot(object, object)
- def onItemNewValue(self, itemName, value):
+ def onItemNewValue(self, itemName: str, value: Any) -> None:
widget = self.delegate.parameters[itemName]
widget.paramWidget.setValue(value)
class ProfilesManager(QtWidgets.QComboBox):
-
#: Signal()
#: Emitted when the selected index changed.
indexChanged = QtCore.Signal()
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.setEditable(False)
- self.params = self.parent().instrument
+ self.params = self.parent().instrument # type: ignore[union-attr]
self.refreshing = False
loadingProfile = None
@@ -456,7 +561,7 @@ def __init__(self, *args, **kwargs):
self.currentIndexChanged.connect(self.onCurrentIndexChanged)
- def refresh(self):
+ def refresh(self) -> None:
self.refreshing = True
currentlySelected = self.currentText()
self.clear()
@@ -467,7 +572,7 @@ def refresh(self):
self.refreshing = False
@QtCore.Slot(int)
- def onCurrentIndexChanged(self, index):
+ def onCurrentIndexChanged(self, index: int) -> None:
if not self.refreshing:
self.indexChanged.emit()
@@ -481,8 +586,19 @@ class ParameterManagerGui(InstrumentParameters):
#: emitted when a parameter was created successfully
parameterCreated = QtCore.Signal()
- def __init__(self, instrument: Union[ProxyInstrument, ParameterManager], parent=None, **kwargs):
- super().__init__(instrument, parent=None, viewType=ParameterManagerTreeView, callSignals=False, **kwargs)
+ def __init__(
+ self,
+ instrument: Union[ProxyInstrument, ParameterManager],
+ parent: Optional[QtWidgets.QWidget] = None,
+ **kwargs: Any,
+ ) -> None:
+ super().__init__(
+ instrument,
+ parent=None,
+ viewType=ParameterManagerTreeView,
+ callSignals=False,
+ **kwargs,
+ )
self.profileManager = ProfilesManager(parent=self)
self.addParam = AddParameterWidget(parent=self)
layout = self.layout()
@@ -492,7 +608,7 @@ def __init__(self, instrument: Union[ProxyInstrument, ParameterManager], parent=
self.connectSignals()
self.loadProfile()
- def connectSignals(self):
+ def connectSignals(self) -> None:
super().connectSignals()
self.view.delegate.removeParameter.connect(self.removeParameter)
self.addParam.newParamRequested.connect(self.addParameter)
@@ -500,7 +616,7 @@ def connectSignals(self):
self.parameterCreated.connect(self.addParam.clear)
self.profileManager.indexChanged.connect(self.loadProfile)
- def makeToolbar(self):
+ def makeToolbar(self) -> QtWidgets.QToolBar:
toolbar = super().makeToolbar()
toolbar.addSeparator()
@@ -509,46 +625,49 @@ def makeToolbar(self):
QtGui.QIcon(":/icons/load.svg"),
"Load parameters from file",
)
- loadParamAction.triggered.connect(lambda x: self.loadFromFile())
+ loadParamAction.triggered.connect(lambda x: self.loadFromFile()) # type: ignore[union-attr]
saveParamAction = toolbar.addAction(
QtGui.QIcon(":/icons/save.svg"),
"Save parameters to file",
)
- saveParamAction.triggered.connect(lambda x: self.saveToFile())
+ saveParamAction.triggered.connect(lambda x: self.saveToFile()) # type: ignore[union-attr]
return toolbar
- def refreshAll(self):
+ def refreshAll(self) -> None:
super().refreshAll()
self.instrument.refresh_profiles()
self.profileManager.refresh()
- def removeParameter(self, fullName: str):
+ def removeParameter(self, fullName: str) -> None:
if self.instrument.has_param(fullName):
self.instrument.remove_parameter(fullName)
- def addParameter(self, fullName, value, unit):
+ def addParameter(self, fullName: str, value: Any, unit: str) -> None:
try:
# Validators are commented out until they can be serialized.
- self.instrument.add_parameter(fullName, initial_value=value,
- unit=unit, ) # vals=vals)
+ self.instrument.add_parameter(
+ fullName,
+ initial_value=value,
+ unit=unit,
+ ) # vals=vals)
self.parameterCreated.emit()
except Exception as e:
- self.parameterCreationError.emit(f"Could not create parameter."
- f"Adding parameter raised"
- f"{type(e)}: {e.args}")
+ self.parameterCreationError.emit(
+ f"Could not create parameter.Adding parameter raised{type(e)}: {e.args}"
+ )
return
@QtCore.Slot()
- def loadProfile(self):
+ def loadProfile(self) -> None:
profileName = self.profileManager.currentText()
self.instrument.switch_to_profile(profileName)
super().refreshAll()
self.instrument.refresh_profiles()
@QtCore.Slot()
- def loadFromFile(self, loadFile=None):
+ def loadFromFile(self, loadFile: Optional[str] = None) -> None:
try:
self.instrument.fromFile(filePath=loadFile, deleteMissing=False)
self.refreshAll()
@@ -557,7 +676,7 @@ def loadFromFile(self, loadFile=None):
logger.info(f"Loading failed. {type(e)}: {e.args}")
@QtCore.Slot()
- def saveToFile(self):
+ def saveToFile(self) -> None:
try:
self.instrument.toFile()
except Exception as e:
@@ -570,13 +689,14 @@ def saveToFile(self):
class MethodsModel(InstrumentModelBase):
-
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.setColumnCount(2)
- self.setHorizontalHeaderLabels([self.attr, 'Arguments & Run'])
+ self.setHorizontalHeaderLabels([self.attr, "Arguments & Run"])
- def insertItemTo(self, parent, item):
+ def insertItemTo(
+ self, parent: QtGui.QStandardItem, item: QtGui.QStandardItem
+ ) -> None:
if item is not None:
extraItem = QtGui.QStandardItem()
@@ -591,33 +711,41 @@ def insertItemTo(self, parent, item):
class MethodsDelegate(DelegateBase):
-
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtCore.QObject] = None) -> None:
super().__init__(parent=parent)
- self.methods = {}
+ self.methods: Dict[str, "MethodDisplay"] = {}
- def createEditor(self, widget: QtWidgets.QWidget, option: QtWidgets.QStyleOptionViewItem,
- index: QtCore.QModelIndex) -> QtWidgets.QWidget:
+ def createEditor( # type: ignore[override]
+ self,
+ widget: QtWidgets.QWidget,
+ option: QtWidgets.QStyleOptionViewItem,
+ index: QtCore.QModelIndex,
+ ) -> QtWidgets.QWidget:
item = self.getItem(index)
- element = item.element
- ret = MethodDisplay(element, item.name, parent=widget)
+ element = item.element # type: ignore[attr-defined]
+ ret = MethodDisplay(element, item.name, parent=widget) # type: ignore[attr-defined]
parent = self.parent()
- assert hasattr(parent, 'clearAlertsAction')
+ assert hasattr(parent, "clearAlertsAction")
# connecting the widget with the clear alert signal
- parent.clearAlertsAction.triggered.connect(ret.alertLabel.clearAlert)
+ parent.clearAlertsAction.triggered.connect(ret.alertLabel.clearAlert) # type: ignore[union-attr]
- self.methods[item.name] = ret
+ self.methods[item.name] = ret # type: ignore[attr-defined]
return ret
class MethodsTreeView(InstrumentTreeViewBase):
- def __init__(self, model, *args, **kwargs):
+ def __init__(
+ self,
+ model: QtCore.QAbstractItemModel,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(model, [1], *args, **kwargs)
# Adding the clear alert to the context menu
- self.clearAlertsAction = QtWidgets.QAction('Clear alerts')
+ self.clearAlertsAction = QtWidgets.QAction("Clear alerts")
self.contextMenu.addSeparator()
self.contextMenu.addAction(self.clearAlertsAction)
@@ -627,24 +755,25 @@ def __init__(self, model, *args, **kwargs):
class InstrumentMethods(InstrumentDisplayBase):
-
- def __init__(self, instrument, **kwargs):
- if 'instrument' in kwargs:
- del kwargs['instrument']
+ def __init__(self, instrument: Any, **kwargs: Any) -> None:
+ if "instrument" in kwargs:
+ del kwargs["instrument"]
modelKwargs = {}
- if 'methods-star' in kwargs:
- modelKwargs['itemsStar'] = kwargs.pop('methods-star')
- if 'methods-trash' in kwargs:
- modelKwargs['itemsTrash'] = kwargs.pop('methods-trash')
- if 'methods-hide' in kwargs:
- modelKwargs['itemsHide'] = kwargs.pop('methods-hide')
-
- super().__init__(instrument=instrument,
- attr='functions',
- modelType=MethodsModel,
- viewType=MethodsTreeView,
- **modelKwargs)
+ if "methods-star" in kwargs:
+ modelKwargs["itemsStar"] = kwargs.pop("methods-star")
+ if "methods-trash" in kwargs:
+ modelKwargs["itemsTrash"] = kwargs.pop("methods-trash")
+ if "methods-hide" in kwargs:
+ modelKwargs["itemsHide"] = kwargs.pop("methods-hide")
+
+ super().__init__(
+ instrument=instrument,
+ attr="functions",
+ modelType=MethodsModel,
+ viewType=MethodsTreeView,
+ **modelKwargs,
+ )
# ----------------- Methods Display Classes - Ending -----------------------------------
@@ -655,22 +784,27 @@ class GenericInstrument(QtWidgets.QWidget):
Widget that allows the display of real time parameters and changing their values.
"""
- def __init__(self, ins: Union[ProxyInstrument, Instrument], parent=None, **modelKwargs):
+ def __init__(
+ self,
+ ins: Union[ProxyInstrument, Instrument],
+ parent: Optional[QtWidgets.QWidget] = None,
+ **modelKwargs: Any,
+ ) -> None:
super().__init__(parent=parent)
self.ins = ins
if type(ins) is ProxyInstrument:
- inst_type = "Proxy-" + ins.bp.instrument_module_class.split('.')[-1]
+ inst_type = "Proxy-" + ins.bp.instrument_module_class.split(".")[-1]
else:
inst_type = ins.__class__.__name__
-
- ins_label = f'{ins.name} | type: {inst_type}'
+
+ ins_label = f"{ins.name} | type: {inst_type}"
try:
# added a unique device_id if the instrument has that method
device_id = ins.device_id()
- ins_label += f' | id: {device_id}'
+ ins_label += f" | id: {device_id}"
except AttributeError:
pass
@@ -678,7 +812,7 @@ def __init__(self, ins: Union[ProxyInstrument, Instrument], parent=None, **model
self.setLayout(self._layout)
self.splitter = QtWidgets.QSplitter(self)
- self.splitter.setOrientation(QtCore.Qt.Vertical)
+ self.splitter.setOrientation(QtCore.Qt.Orientation.Vertical)
self.parametersList = InstrumentParameters(instrument=ins, **modelKwargs)
self.methodsList = InstrumentMethods(instrument=ins, **modelKwargs)
@@ -688,9 +822,15 @@ def __init__(self, ins: Union[ProxyInstrument, Instrument], parent=None, **model
self._layout.addWidget(self.splitter)
self.splitter.addWidget(self.parametersList)
self.splitter.addWidget(self.methodsList)
-
# Resize param name, unit, and function name columns after entries loaded
self.parametersList.view.resizeColumnToContents(0)
self.parametersList.view.resizeColumnToContents(1)
self.methodsList.view.resizeColumnToContents(0)
+
+ def closeEvent(self, event: QtGui.QCloseEvent) -> None: # type: ignore[override]
+ """Stop the parameter subscriber thread before destruction."""
+ model = getattr(self.parametersList, "model", None)
+ if model is not None and hasattr(model, "stopListener"):
+ model.stopListener()
+ super().closeEvent(event)
diff --git a/instrumentserver/gui/misc.py b/src/instrumentserver/gui/misc.py
similarity index 63%
rename from instrumentserver/gui/misc.py
rename to src/instrumentserver/gui/misc.py
index 3d6ee29..9e7f6fb 100644
--- a/instrumentserver/gui/misc.py
+++ b/src/instrumentserver/gui/misc.py
@@ -1,59 +1,74 @@
-from typing import Optional, Tuple
+from typing import Any, Optional, Tuple, cast
-from .. import QtWidgets, QtGui, QtCore
+from .. import QtCore, QtGui, QtWidgets
class AlertLabel(QtWidgets.QLabel):
-
- def __init__(self, parent: Optional[QtWidgets.QWidget] = None, pixmapSize: Tuple[int, int] = (20, 20)):
+ def __init__(
+ self,
+ parent: Optional[QtWidgets.QWidget] = None,
+ pixmapSize: Tuple[int, int] = (20, 20),
+ ):
super().__init__(parent)
- self.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignHCenter)
+ self.setAlignment(
+ cast(
+ "QtCore.Qt.Alignment",
+ QtCore.Qt.AlignmentFlag.AlignVCenter
+ | QtCore.Qt.AlignmentFlag.AlignHCenter,
+ )
+ )
self._pixmapSize = pixmapSize
pix = QtGui.QIcon(":/icons/no-alert.svg").pixmap(*pixmapSize)
self.setPixmap(pix)
- self.setToolTip('no alerts')
+ self.setToolTip("no alerts")
@QtCore.Slot(str)
- def setAlert(self, message: str):
+ def setAlert(self, message: str) -> None:
pix = QtGui.QIcon(":/icons/red-alert.svg").pixmap(*self._pixmapSize)
self.setPixmap(pix)
self.setToolTip(message)
@QtCore.Slot()
- def clearAlert(self):
+ def clearAlert(self) -> None:
pix = QtGui.QIcon(":/icons/no-alert.svg").pixmap(*self._pixmapSize)
self.setPixmap(pix)
- self.setToolTip('no alerts')
+ self.setToolTip("no alerts")
class AlertLabelGreen(AlertLabel):
"""
Expanding the functionality of the AlertLabel to add green alerts to indicate successful things
"""
- def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
+
+ def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None: # type: ignore[override]
self.clearAlert()
super().mouseDoubleClickEvent(a0)
- def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None:
- if ev.buttons() == QtCore.Qt.MidButton:
+ def mousePressEvent(self, ev: QtGui.QMouseEvent) -> None: # type: ignore[override]
+ if ev.buttons() == QtCore.Qt.MouseButton.MidButton:
self.clearAlert()
super().mousePressEvent(ev)
@QtCore.Slot(str)
- def setSuccssefulAlert(self, message: str):
+ def setSuccssefulAlert(self, message: str) -> None:
pix = QtGui.QIcon(":/icons/green-alert.svg").pixmap(*self._pixmapSize)
self.setPixmap(pix)
self.setToolTip(message)
class DetachedTab(QtWidgets.QMainWindow):
-
#: Signal(QtWidgets.QWidget)
#: emitted when a tab for the instrument is closed
onCloseSignal = QtCore.Signal(object, str)
- def __init__(self, contentWidget: QtWidgets.QWidget, name: str, *args, **kwargs):
+ def __init__(
+ self,
+ contentWidget: QtWidgets.QWidget,
+ name: str,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(*args, **kwargs)
self.name = name
@@ -65,12 +80,11 @@ def __init__(self, contentWidget: QtWidgets.QWidget, name: str, *args, **kwargs)
self.widget.show()
- def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
+ def closeEvent(self, a0: QtGui.QCloseEvent) -> None: # type: ignore[override]
self.onCloseSignal.emit(self.widget, self.name)
class SeparableTabBar(QtWidgets.QTabBar):
-
#: Signal(tabIndex, newPosition)
#: Emitted when the user is dragging a tab out ofd the tab bar and should be detached.
onDetachTab = QtCore.Signal(object, object)
@@ -79,16 +93,16 @@ class SeparableTabBar(QtWidgets.QTabBar):
#: Emitted when the user is moving the tabs.
onMoveTab = QtCore.Signal(int, int)
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.selectedIndex = 0
self.dragStartPos = QtCore.QPoint()
self.dragDroppedPos = QtCore.QPoint()
- self.setElideMode(QtCore.Qt.ElideRight)
+ self.setElideMode(QtCore.Qt.TextElideMode.ElideRight)
self.setAcceptDrops(True)
- def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
- if a0.button() == QtCore.Qt.LeftButton:
+ def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None: # type: ignore[override]
+ if a0.button() == QtCore.Qt.MouseButton.LeftButton:
self.dragStartPos = a0.pos()
self.dragDroppedPos.setX(0)
@@ -97,64 +111,70 @@ def mousePressEvent(self, a0: QtGui.QMouseEvent) -> None:
self.selectedIndex = self.tabAt(self.dragStartPos)
super().mousePressEvent(a0)
- def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
+ def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None: # type: ignore[override]
self.onDetachTab.emit(self.tabAt(a0.pos()), a0.globalPos())
a0.accept()
- def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
+ def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None: # type: ignore[override]
"""
Detects if the user is dragging a tab and starts the drag object.
"""
- if (a0.pos() - self.dragStartPos).manhattanLength() > QtWidgets.QApplication.startDragDistance() \
- and self.selectedIndex != -1:
-
+ if (
+ (a0.pos() - self.dragStartPos).manhattanLength()
+ > QtWidgets.QApplication.startDragDistance()
+ and self.selectedIndex != -1
+ ):
drag = QtGui.QDrag(self)
mimeData = QtCore.QMimeData()
- mimeData.setData('action', b'application/tab-detach')
+ mimeData.setData("action", b"application/tab-detach")
drag.setMimeData(mimeData)
- pixmap = self.parentWidget().currentWidget().grab() # type: ignore[attr-defined] # I am pretty sure the stubs are wrong for this one, running through the debugger all the methods exists.
+ pixmap = self.parentWidget().currentWidget().grab() # type: ignore[union-attr]
targetPixmap = QtGui.QPixmap(pixmap.size())
- targetPixmap.fill(QtCore.Qt.transparent)
+ targetPixmap.fill(QtCore.Qt.GlobalColor.transparent)
painter = QtGui.QPainter(targetPixmap)
painter.drawPixmap(0, 0, pixmap)
painter.end()
drag.setPixmap(targetPixmap)
- dropAction = drag.exec_(QtCore.Qt.MoveAction | QtCore.Qt.CopyAction)
+ dropAction = drag.exec_(
+ QtCore.Qt.DropAction.MoveAction | QtCore.Qt.DropAction.CopyAction
+ ) # type: ignore[call-overload]
# In linux the drag.exec_ does not return MoveAction, so it must be set manually.
if self.dragDroppedPos.x() != 0 and self.dragDroppedPos.y() != 0:
- dropAction = QtCore.Qt.MoveAction
+ dropAction = QtCore.Qt.DropAction.MoveAction
# A move action indicates that the user is trying to move the tabs around
- if dropAction == QtCore.Qt.MoveAction:
+ if dropAction == QtCore.Qt.DropAction.MoveAction:
a0.accept()
- self.onMoveTab.emit(self.tabAt(self.dragStartPos), self.tabAt(self.dragDroppedPos))
+ self.onMoveTab.emit(
+ self.tabAt(self.dragStartPos), self.tabAt(self.dragDroppedPos)
+ )
# An ignore action means that the user dropped the tab outside of the window and should be detached.
- elif dropAction == QtCore.Qt.IgnoreAction:
+ elif dropAction == QtCore.Qt.DropAction.IgnoreAction:
a0.accept()
self.onDetachTab.emit(self.selectedIndex, self.cursor().pos())
else:
super().mouseMoveEvent(a0)
- def dragEnterEvent(self, a0: QtGui.QDragEnterEvent) -> None:
+ def dragEnterEvent(self, a0: QtGui.QDragEnterEvent) -> None: # type: ignore[override]
mimeData = a0.mimeData()
- formats = mimeData.formats()
+ formats = mimeData.formats() # type: ignore[union-attr]
- if 'action' in formats and mimeData.data('action') == 'application/tab-detach':
+ if "action" in formats and mimeData.data("action") == "application/tab-detach": # type: ignore[union-attr]
a0.acceptProposedAction()
super().dragMoveEvent(a0)
- def dropEvent(self, a0: QtGui.QDropEvent) -> None:
+ def dropEvent(self, a0: QtGui.QDropEvent) -> None: # type: ignore[override]
self.dragDroppedPos = a0.pos()
super().dropEvent(a0)
- def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:
+ def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None: # type: ignore[override]
a0.accept()
super().mouseReleaseEvent(a0)
@@ -170,7 +190,7 @@ class DetachableTabWidget(QtWidgets.QTabWidget):
#: Emitted when a tab got closed.
onTabClosed = QtCore.Signal(str)
- def __init__(self, *args, **kwargs):
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self._tabBar = SeparableTabBar(self)
self._tabBar.setTabsClosable(True)
@@ -179,43 +199,47 @@ def __init__(self, *args, **kwargs):
self._tabBar.onDetachTab.connect(self.onDetachTab)
self._tabBar.onMoveTab.connect(self.onMoveTab)
- self.unclosableTabs = {}
+ self.unclosableTabs: dict[str, QtWidgets.QWidget] = {}
- def addUnclosableTab(self, widget, name):
+ def addUnclosableTab(self, widget: QtWidgets.QWidget, name: str) -> None:
index = self.addTab(widget, name)
- closeButton = self._tabBar.tabButton(index, QtWidgets.QTabBar.ButtonPosition.RightSide)
+ closeButton = self._tabBar.tabButton(
+ index, QtWidgets.QTabBar.ButtonPosition.RightSide
+ )
# on Mac the button is on the left side
if closeButton is None:
- closeButton = self._tabBar.tabButton(index, QtWidgets.QTabBar.ButtonPosition.LeftSide)
- closeButton.resize(0, 0)
+ closeButton = self._tabBar.tabButton(
+ index, QtWidgets.QTabBar.ButtonPosition.LeftSide
+ )
+ closeButton.resize(0, 0) # type: ignore[union-attr]
self.unclosableTabs[name] = widget
@QtCore.Slot(object, object)
- def onDetachTab(self, tab, point: QtCore.QPoint):
+ def onDetachTab(self, tab: int, point: QtCore.QPoint) -> None:
"""
Gets triggered when the user drags out a tab. Opens a QMainWindow with the widget in the dragged tab.
"""
widget = self.widget(tab)
name = self.tabText(tab)
self.removeTab(self.indexOf(widget))
- detachedTab = DetachedTab(widget, name, parent=self)
+ detachedTab = DetachedTab(widget, name, parent=self) # type: ignore[arg-type]
movedPoint = QtCore.QPoint(point.x(), point.y())
detachedTab.move(movedPoint)
detachedTab.onCloseSignal.connect(self.onAttatchTab)
detachedTab.show()
@QtCore.Slot(object, str)
- def onAttatchTab(self, widget, name):
+ def onAttatchTab(self, widget: QtWidgets.QWidget, name: str) -> None:
"""
Gets called when the user closes one of the detachable windows and properly attaches the tab back.
"""
if name in self.unclosableTabs:
self.addUnclosableTab(widget, name)
else:
- index = self.addTab(widget, name)
+ self.addTab(widget, name)
@QtCore.Slot(int, int)
- def onMoveTab(self, fromIndex, toIndex):
+ def onMoveTab(self, fromIndex: int, toIndex: int) -> None:
widget = self.widget(fromIndex)
icon = self.tabIcon(fromIndex)
text = self.tabText(fromIndex)
@@ -223,11 +247,13 @@ def onMoveTab(self, fromIndex, toIndex):
self.onCloseTab(fromIndex, True)
self.insertTab(toIndex, widget, icon, text)
if text in self.unclosableTabs:
- self._tabBar.tabButton(toIndex, QtWidgets.QTabBar.ButtonPosition.RightSide).resize(0, 0)
+ self._tabBar.tabButton(
+ toIndex, QtWidgets.QTabBar.ButtonPosition.RightSide
+ ).resize(0, 0) # type: ignore[union-attr]
self.setCurrentWidget(widget)
@QtCore.Slot(int)
- def onCloseTab(self, index, moving=False):
+ def onCloseTab(self, index: int, moving: bool = False) -> None:
"""
Closes the tab at index.
@@ -236,7 +262,7 @@ def onCloseTab(self, index, moving=False):
"""
name = self.tabText(index)
widget = self.widget(index)
- widget.close()
+ widget.close() # type: ignore[union-attr]
widget = None
self.removeTab(index)
@@ -253,11 +279,20 @@ class BaseDialog(QtWidgets.QDialog):
:param tittleBarButtonsWidth: The width of pixels that the icon, the width will be set such that it is this number
plus whatever the tittle is plus 15 extra pixels of margin.
"""
- def __init__(self, parent=None, flags=(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowCloseButtonHint), tittleBarButtonsWidth=108):
+
+ def __init__(
+ self,
+ parent: Optional[QtWidgets.QWidget] = None,
+ flags: Any = (
+ QtCore.Qt.WindowType.CustomizeWindowHint
+ | QtCore.Qt.WindowType.WindowCloseButtonHint
+ ),
+ tittleBarButtonsWidth: int = 108,
+ ) -> None:
super().__init__(parent, flags=flags)
self.tittleBarButtonsWidth = tittleBarButtonsWidth
- def setWindowTitle(self, p_str):
+ def setWindowTitle(self, p_str: Optional[str]) -> None:
super().setWindowTitle(p_str)
tittleWidth = self.fontMetrics().boundingRect(p_str).size().width()
minWidth = self.tittleBarButtonsWidth + tittleWidth + 15
diff --git a/instrumentserver/gui/parameters.py b/src/instrumentserver/gui/parameters.py
similarity index 77%
rename from instrumentserver/gui/parameters.py
rename to src/instrumentserver/gui/parameters.py
index 6f75f0e..c1d7f15 100644
--- a/instrumentserver/gui/parameters.py
+++ b/src/instrumentserver/gui/parameters.py
@@ -1,24 +1,24 @@
import logging
-import math
import numbers
-from typing import Any, Optional, List
import re
+from typing import Any, Callable, List, Optional, Tuple
from qcodes import Parameter
+from .. import QtCore, QtGui, QtWidgets
+from ..params import ParameterTypes, paramTypeFromVals
from . import keepSmallHorizontally
from .misc import AlertLabel
-from .. import QtWidgets, QtCore, QtGui, resource
-from ..params import ParameterTypes, paramTypeFromVals
logger = logging.getLogger(__name__)
# TODO: do all styling with a global style sheet
-FLOAT_PRECISION = 10 # The maximum number of significant digits for float numbers
+FLOAT_PRECISION = 10 # The maximum number of significant digits for float numbers
+
-def float_formater(val):
+def float_formater(val: Any) -> str:
"""
For displaying float numbers with scientific notation.
"""
@@ -49,8 +49,12 @@ class ParameterWidget(QtWidgets.QWidget):
#: Signal(Any) --
_valueFromWidget = QtCore.Signal(object)
- def __init__(self, parameter: Parameter, parent=None,
- additionalWidgets: Optional[List[QtWidgets.QWidget]] = None):
+ def __init__(
+ self,
+ parameter: Parameter,
+ parent: Optional[QtWidgets.QWidget] = None,
+ additionalWidgets: Optional[List[QtWidgets.QWidget]] = None,
+ ) -> None:
super().__init__(parent)
@@ -61,8 +65,9 @@ def __init__(self, parameter: Parameter, parent=None,
self._setMethod = lambda x: None
layout = QtWidgets.QGridLayout(self)
- self.getButton = QtWidgets.QPushButton(QtGui.QIcon(":/icons/refresh.svg"),
- "", parent=self)
+ self.getButton = QtWidgets.QPushButton(
+ QtGui.QIcon(":/icons/refresh.svg"), "", parent=self
+ )
self.getButton.pressed.connect(self.setWidgetFromParameter)
keepSmallHorizontally(self.getButton)
layout.addWidget(self.getButton, 0, 1)
@@ -75,8 +80,7 @@ def __init__(self, parameter: Parameter, parent=None,
layout.addWidget(self.alertWidget, 0, 3)
# an input field will only be created if we have a set method.
- if hasattr(parameter, 'set'):
-
+ if hasattr(parameter, "set"):
self.parameterSet.connect(lambda x: self.setButton.setPending(False))
self.parameterSet.connect(lambda x: self.alertWidget.clearAlert())
self.parameterPending.connect(lambda x: self.setButton.setPending(True))
@@ -86,8 +90,13 @@ def __init__(self, parameter: Parameter, parent=None,
# depending on the validator of the parameter, we'll create a fitting
# input widget
ptype = paramTypeFromVals(parameter.vals)
- vals = parameter.vals
- self.paramWidget: NumberInput | AnyInput | QtWidgets.QLineEdit | QtWidgets.QCheckBox | QtWidgets.QLabel
+ self.paramWidget: (
+ NumberInput
+ | AnyInput
+ | QtWidgets.QLineEdit
+ | QtWidgets.QCheckBox
+ | QtWidgets.QLabel
+ )
# FIXME: Currently blueprints don't pass validators meaning that we will never reach any of these if statements.
# This should get uncommented when the blueprints are fixed.
@@ -141,12 +150,17 @@ def __init__(self, parameter: Parameter, parent=None,
else:
self.setButton.setDisabled(True)
self.paramWidget = QtWidgets.QLabel(self)
- self._setMethod = lambda x: self.paramWidget.setText(str(x)) \
- if isinstance(self.paramWidget, QtWidgets.QLabel) else None
- try: # also do immediate update for read-only params, as what we do for the editable parameters above.
+ self._setMethod = lambda x: (
+ self.paramWidget.setText(str(x))
+ if isinstance(self.paramWidget, QtWidgets.QLabel)
+ else None
+ )
+ try: # also do immediate update for read-only params, as what we do for the editable parameters above.
self._setMethod(parameter())
except Exception as e:
- logger.warning(f"Error when setting parameter {parameter}: {e}", exc_info=True)
+ logger.warning(
+ f"Error when setting parameter {parameter}: {e}", exc_info=True
+ )
layout.addWidget(self.paramWidget, 0, 0)
additionalWidgets = additionalWidgets or []
@@ -163,32 +177,32 @@ def __init__(self, parameter: Parameter, parent=None,
self.setLayout(layout)
@QtCore.Slot()
- def onReturnPressed(self):
+ def onReturnPressed(self) -> None:
"""Activates the setButton when the input is selected and enter is pressed."""
self.setButton.click()
self.paramWidget.input.deselect()
self.setButton.setFocus()
-
- def setParameter(self, value: Any):
+ def setParameter(self, value: Any) -> None:
try:
self._parameter.set(value)
except Exception as e:
- self.parameterSetError.emit(f"Could not set parameter, raised {type(e)}:"
- f" {e.args}")
+ self.parameterSetError.emit(
+ f"Could not set parameter, raised {type(e)}: {e.args}"
+ )
return
self.parameterSet.emit(value)
- def setPending(self, value: Any):
+ def setPending(self, value: Any) -> None:
self.parameterPending.emit(value)
@QtCore.Slot()
- def getAndEmitValueFromWidget(self):
+ def getAndEmitValueFromWidget(self) -> None:
self._valueFromWidget.emit(self._getMethod())
@QtCore.Slot()
- def setWidgetFromParameter(self):
+ def setWidgetFromParameter(self) -> None:
val = self._parameter.get()
self._setMethod(val)
self.parameterSet.emit(val)
@@ -199,19 +213,23 @@ class AnyInput(QtWidgets.QWidget):
#: emitted when the input field is changed, argument is the new value.
inputChanged = QtCore.Signal(str)
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super().__init__(parent)
self.input = QtWidgets.QLineEdit()
self.input.textEdited.connect(self._processTextEdited)
self.doEval = QtWidgets.QPushButton(
- QtGui.QIcon(":/icons/python.svg"), "", parent=self,
+ QtGui.QIcon(":/icons/python.svg"),
+ "",
+ parent=self,
)
self.doEval.setCheckable(True)
self.doEval.setChecked(True)
- self.doEval.setToolTip("Evaluate input as python expression.\n"
- "If evaluation fails, treat as string.")
+ self.doEval.setToolTip(
+ "Evaluate input as python expression.\n"
+ "If evaluation fails, treat as string."
+ )
keepSmallHorizontally(self.doEval)
layout = QtWidgets.QHBoxLayout(self)
@@ -224,38 +242,40 @@ def __init__(self, parent=None):
QPushButton:checked { background-color: palegreen }
""")
- def value(self):
+ def value(self) -> Any:
if self.doEval.isChecked():
try:
ret = eval(self.input.text())
- except Exception as e:
+ except Exception:
ret = self.input.text()
return ret
else:
return self.input.text()
- def setValue(self, val: Any):
+ def setValue(self, val: Any) -> None:
try:
self.input.setText(float_formater(val))
except RuntimeError as e:
- logger.debug(f"Could not set value {val} in AnyInput element does not exists, raised {type(e)}: {e.args}")
+ logger.debug(
+ f"Could not set value {val} in AnyInput element does not exists, raised {type(e)}: {e.args}"
+ )
@QtCore.Slot(str)
- def _processTextEdited(self, val: str):
+ def _processTextEdited(self, val: str) -> None:
self.inputChanged.emit(val)
class NumberInput(QtWidgets.QLineEdit):
"""A text edit widget that checks whether its input can be read as a number."""
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super().__init__(parent)
self.textChanged.connect(self.checkIfNumber)
- def checkIfNumber(self, value: str):
+ def checkIfNumber(self, value: str) -> None:
try:
val = eval(value)
- except:
+ except Exception:
val = None
if not isinstance(val, numbers.Number):
@@ -267,21 +287,24 @@ def checkIfNumber(self, value: str):
NumberInput { }
""")
- def value(self):
+ def value(self) -> Optional[numbers.Number]:
try:
value = eval(self.text())
- except:
+ except Exception:
return None
if isinstance(value, numbers.Number):
return value
else:
return None
- def setValue(self, value: numbers.Number):
+ def setValue(self, value: numbers.Number) -> None:
try:
self.setText(float_formater(value))
except RuntimeError as e:
- logger.debug(f"Could not set value {value} in NumberInput, raised {type(e)}: {e.args}")
+ logger.debug(
+ f"Could not set value {value} in NumberInput, raised {type(e)}: {e.args}"
+ )
+
class AnyInputForMethod(AnyInput):
"""
@@ -292,17 +315,18 @@ class AnyInputForMethod(AnyInput):
All arguments and keyword arguments are evaluated if the doEval button is checked, if not everything is treated like
a long string.
"""
- def value(self):
+
+ def value(self) -> Tuple[Any, Any]:
if self.doEval.isChecked():
# If '=' is present we need to separate the keyword from the value
# If ',' is present we have more than one argument.
- if '=' in self.input.text() or ',' in self.input.text():
- rawArgs = self.input.text().split(',')
+ if "=" in self.input.text() or "," in self.input.text():
+ rawArgs = self.input.text().split(",")
args = []
kwargs = {}
for x in rawArgs:
- if '=' in x:
- key, value = x.split('=')
+ if "=" in x:
+ key, value = x.split("=")
key = key.replace(" ", "")
kwargs[key] = eval(value)
else:
@@ -315,9 +339,8 @@ def value(self):
class SetButton(QtWidgets.QPushButton):
-
@QtCore.Slot(bool)
- def setPending(self, isPending: bool):
+ def setPending(self, isPending: bool) -> None:
if isPending:
self.setStyleSheet("SetButton { background-color: orange }")
else:
diff --git a/instrumentserver/helpers.py b/src/instrumentserver/helpers.py
similarity index 81%
rename from instrumentserver/helpers.py
rename to src/instrumentserver/helpers.py
index dd2df0f..c4e2e89 100644
--- a/instrumentserver/helpers.py
+++ b/src/instrumentserver/helpers.py
@@ -1,11 +1,10 @@
import inspect
-from typing import Dict, Any, List, Union, Tuple
+from typing import Any, Dict, List, Tuple, Union
from qcodes import Instrument, Parameter
from .serialize import toParamDict
-
# TODO: check for usage of get params / methods functions -- might not be needed
@@ -23,15 +22,15 @@ def stringToArgsAndKwargs(value: str) -> Tuple[List[Any], Dict[str, Any]]:
- args or kwarg values cannot be evaluated with ``eval``.
"""
value = value.strip()
- if value == '':
+ if value == "":
return [], {}
args = []
kwargs = {}
- elts = [v.strip() for v in value.split(',')]
+ elts = [v.strip() for v in value.split(",")]
for elt in elts:
- if '=' in elt:
- keyandval = elt.split('=')
+ if "=" in elt:
+ keyandval = elt.split("=")
if len(keyandval) != 2:
raise ValueError(f"{elt} cannot be interpreted as kwarg")
try:
@@ -47,11 +46,11 @@ def stringToArgsAndKwargs(value: str) -> Tuple[List[Any], Dict[str, Any]]:
return args, kwargs
-def typeClassPath(t) -> str:
+def typeClassPath(t: type) -> str:
return f"{t.__module__}.{t.__qualname__}"
-def objectClassPath(o) -> str:
+def objectClassPath(o: Any) -> str:
return f"{o.__class__.__module__}.{o.__class__.__qualname__}"
@@ -62,7 +61,7 @@ def nestedAttributeFromString(root: Any, loc: str) -> Any:
returns the object that can be found at parent_object.foo.bar.spam.bacon.
"""
- mods = loc.split('.')
+ mods = loc.split(".")
obj = root
for m in mods:
obj = getattr(obj, m)
@@ -76,13 +75,15 @@ def getInstrumentParameters(ins: Instrument) -> Dict[str, Dict[str, str]]:
:returns: a param dict with entries `unit`, `vals`, for each
instrument parameter.
"""
- paramDict = toParamDict([ins], includeMeta=['unit', 'vals'])
+ paramDict = toParamDict([ins], includeMeta=["unit", "vals"])
for k, v in paramDict.items():
- paramDict[k].pop('value', None)
+ paramDict[k].pop("value", None)
return paramDict
-def getInstrumentMethods(ins: Instrument) -> Dict[str, Dict[str, Union[str, List[str]]]]:
+def getInstrumentMethods(
+ ins: Instrument,
+) -> Dict[str, Dict[str, Union[str, List[str]]]]:
"""Return the methods of an instrument.
:param ins: instrument instance
@@ -94,7 +95,7 @@ def getInstrumentMethods(ins: Instrument) -> Dict[str, Dict[str, Union[str, List
"""
funcs: dict = {}
for attr_name in dir(ins):
- if attr_name[0] != '_' and attr_name not in dir(Instrument):
+ if attr_name[0] != "_" and attr_name not in dir(Instrument):
obj = getattr(ins, attr_name)
if callable(obj) and not isinstance(obj, Parameter):
funcs[attr_name] = dict()
@@ -102,10 +103,11 @@ def getInstrumentMethods(ins: Instrument) -> Dict[str, Dict[str, Union[str, List
for fname in funcs.keys():
fun = getattr(ins, fname)
signature = inspect.signature(fun)
- funcs[fname]['parameters'] = [str(signature.parameters[a]) for a in
- signature.parameters]
- funcs[fname]['doc'] = str(fun.__doc__)
- funcs[fname]['return'] = str(signature.return_annotation)
+ funcs[fname]["parameters"] = [
+ str(signature.parameters[a]) for a in signature.parameters
+ ]
+ funcs[fname]["doc"] = str(fun.__doc__)
+ funcs[fname]["return"] = str(signature.return_annotation)
return funcs
@@ -132,9 +134,9 @@ def flat_to_nested_dict(flat_dict: Dict) -> Dict:
# result:
{"a": {"b": {"c": 1,"d": 2}},"x": 3}
"""
- nested = {}
+ nested: Dict[str, Any] = {}
for key, value in flat_dict.items():
- parts = key.split('.')
+ parts = key.split(".")
d = nested
for part in parts[:-1]:
d = d.setdefault(part, {})
@@ -142,13 +144,14 @@ def flat_to_nested_dict(flat_dict: Dict) -> Dict:
return nested
-def is_flat_dict(d:dict) -> bool:
+def is_flat_dict(d: dict) -> bool:
"""
Detects if a dictionary is flat (i.e. all values are non-dicts).
"""
return all(not isinstance(v, dict) for v in d.values())
-def flatten_dict(d, sep='.'):
+
+def flatten_dict(d: Dict[str, Any], sep: str = ".") -> Dict[str, Any]:
"""
Detects if a dictionary is flat (i.e. all values are non-dicts).
If it is not flat, recursively flattens it using dot-separated keys.
@@ -171,8 +174,8 @@ def flatten_dict(d, sep='.'):
if is_flat_dict(d):
return d
- def flatten(nested, parent_key=''):
- items = {}
+ def flatten(nested: Dict[str, Any], parent_key: str = "") -> Dict[str, Any]:
+ items: Dict[str, Any] = {}
for k, v in nested.items():
new_key = f"{parent_key}{sep}{k}" if parent_key else k
if isinstance(v, dict):
@@ -181,4 +184,4 @@ def flatten(nested, parent_key=''):
items[new_key] = v
return items
- return flatten(d)
\ No newline at end of file
+ return flatten(d)
diff --git a/instrumentserver/log.py b/src/instrumentserver/log.py
similarity index 52%
rename from instrumentserver/log.py
rename to src/instrumentserver/log.py
index cc5230e..2c0b5ca 100644
--- a/instrumentserver/log.py
+++ b/src/instrumentserver/log.py
@@ -2,13 +2,14 @@
instrumentserver.log : Logging tools and defaults for instrumentserver.
"""
-import sys
import logging
+import re
+import sys
from enum import Enum, auto, unique
from html import escape
-import re
+from typing import Callable, Optional
-from . import QtGui, QtWidgets, QtCore
+from . import QtCore, QtGui, QtWidgets
@unique
@@ -19,93 +20,108 @@ class LogLevels(Enum):
debug = auto()
-class QLogHandler(QtCore.QObject,logging.Handler):
+class QLogHandler(QtCore.QObject, logging.Handler):
"""A simple log handler that supports logging in TextEdit"""
COLORS = {
- logging.ERROR: QtGui.QColor('red'),
- logging.WARNING: QtGui.QColor('orange'),
- logging.INFO: QtGui.QColor('green'),
- logging.DEBUG: QtGui.QColor('gray'),
+ logging.ERROR: QtGui.QColor("red"),
+ logging.WARNING: QtGui.QColor("orange"),
+ logging.INFO: QtGui.QColor("green"),
+ logging.DEBUG: QtGui.QColor("gray"),
}
-
+
new_html = QtCore.Signal(str)
- def __init__(self, parent):
+ def __init__(self, parent: Optional[QtWidgets.QWidget]) -> None:
QtCore.QObject.__init__(self, parent)
logging.Handler.__init__(self)
self.widget = QtWidgets.QTextEdit(parent)
self.widget.setReadOnly(True)
- self._transform = None
+ self._transform: Optional[Callable[[logging.LogRecord, str], Optional[str]]] = (
+ None
+ )
# connect signal to slot that actually touches the widget (GUI thread)
self.new_html.connect(self._append_html)
-
-
+
@QtCore.Slot(str)
- def _append_html(self, html: str):
+ def _append_html(self, html: str) -> None:
"""Append HTML to the text widget in the GUI thread."""
self.widget.append(html)
# reset char format so bold/italics don’t bleed into the next line
self.widget.setCurrentCharFormat(QtGui.QTextCharFormat())
# keep view scrolled to bottom
- self.widget.verticalScrollBar().setValue(
- self.widget.verticalScrollBar().maximum()
+ self.widget.verticalScrollBar().setValue( # type: ignore[union-attr]
+ self.widget.verticalScrollBar().maximum() # type: ignore[union-attr]
)
-
- def set_transform(self, fn):
+ def set_transform(
+ self, fn: Callable[[logging.LogRecord, str], Optional[str]]
+ ) -> None:
"""fn(record, msg) -> str | {'html': str} | None"""
self._transform = fn
- def emit(self, record):
- formatted = self.format(record) # prefix + message
- raw_msg = record.getMessage() # message only
-
- # Color for prefix (log level)
- clr = self.COLORS.get(record.levelno, QtGui.QColor('black')).name()
-
- if self._transform is not None:
- html_fragment = self._transform(record, raw_msg)
- if html_fragment:
- i = formatted.rfind(raw_msg)
- if i >= 0:
- prefix = formatted[:i]
- suffix = formatted[i + len(raw_msg):]
- else:
- prefix, suffix = "", ""
-
- # Build HTML line
- html = (
- f"{escape(prefix)}"
- f"{html_fragment}"
- f"{escape(suffix)}"
- )
-
- # send to GUI thread
- self.new_html.emit(html)
- return
-
- # fallback: original plain text path
- msg = formatted
- clr_q = self.COLORS.get(record.levelno, QtGui.QColor('black')).name()
- html = f"{escape(msg)}"
-
- self.new_html.emit(html)
+ def emit(self, record: logging.LogRecord) -> None:
+ try:
+ formatted = self.format(record) # prefix + message
+ raw_msg = record.getMessage() # message only
+
+ # Color for prefix (log level)
+ clr = self.COLORS.get(record.levelno, QtGui.QColor("black")).name()
+
+ if self._transform is not None:
+ html_fragment = self._transform(record, raw_msg)
+ if html_fragment:
+ i = formatted.rfind(raw_msg)
+ if i >= 0:
+ prefix = formatted[:i]
+ suffix = formatted[i + len(raw_msg) :]
+ else:
+ prefix, suffix = "", ""
+
+ # Build HTML line
+ html = (
+ f"{escape(prefix)}"
+ f"{html_fragment}"
+ f"{escape(suffix)}"
+ )
+
+ # send to GUI thread
+ self.new_html.emit(html)
+ return
+
+ # fallback: original plain text path
+ msg = formatted
+ clr_q = self.COLORS.get(record.levelno, QtGui.QColor("black")).name()
+ html = f"{escape(msg)}"
+
+ self.new_html.emit(html)
+ except RuntimeError:
+ # Widget has been destroyed; detach self from the logger so we
+ # stop receiving further records and Python can collect us.
+ for lg in list(logging.Logger.manager.loggerDict.values()) + [
+ logging.getLogger()
+ ]:
+ if isinstance(lg, logging.Logger) and self in lg.handlers:
+ lg.removeHandler(self)
+
class LogWidget(QtWidgets.QWidget):
"""
A simple logger widget. Uses QLogHandler as handler.
The handler has the actual widget that is used to display the logs.
"""
- def __init__(self, parent=None, level=logging.INFO):
+
+ def __init__(
+ self, parent: Optional[QtWidgets.QWidget] = None, level: int = logging.INFO
+ ) -> None:
super().__init__(parent)
# set up the graphical handler
fmt = logging.Formatter(
"[%(asctime)s] [%(name)s: %(levelname)s] %(message)s",
- datefmt='%m-%d %H:%M:%S',
+ datefmt="%m-%d %H:%M:%S",
)
logTextBox = QLogHandler(self)
logTextBox.setFormatter(fmt)
@@ -119,7 +135,7 @@ def __init__(self, parent=None, level=logging.INFO):
self.setLayout(layout)
# configure the logger
- self.logger = logging.getLogger('instrumentserver')
+ self.logger = logging.getLogger("instrumentserver")
# delete old graphical handler. however, that would allow only one
# graphical handler per kernel. not sure i want that...?
@@ -133,15 +149,17 @@ def __init__(self, parent=None, level=logging.INFO):
self.handler.set_transform(_param_update_formatter)
-def _param_update_formatter(record, raw_msg):
+def _param_update_formatter(record: logging.LogRecord, raw_msg: str) -> Optional[str]:
"""
A formater that makes parameter updates more prominent in the gui log window.
"""
# Pattern 1: "parameter-update" from the broadcaster, for client station
- pattern_update = re.compile(r'parameter-update:\s*([A-Za-z0-9_.]+):\s*(.+)', re.S)
+ pattern_update = re.compile(r"parameter-update:\s*([A-Za-z0-9_.]+):\s*(.+)", re.S)
# Pattern 2: normal log message from the server. i.e. `Parameter {name} set to: {value}`
- pattern_info = re.compile(r"Parameter\s+'([A-Za-z0-9_.]+)'\s+set\s+to:\s*(.+)", re.S)
+ pattern_info = re.compile(
+ r"Parameter\s+'([A-Za-z0-9_.]+)'\s+set\s+to:\s*(.+)", re.S
+ )
match = pattern_update.search(raw_msg) or pattern_info.search(raw_msg)
if not match:
@@ -150,12 +168,18 @@ def _param_update_formatter(record, raw_msg):
name, value = match.groups()
# Escape HTML but keep \n literal (QTextEdit.append will render them)
- return ( f"{escape(name)} set to: " f"{escape(value)}" )
-
-
-def setupLogging(addStreamHandler=True, logFile=None,
- name='instrumentserver',
- streamHandlerLevel=logging.INFO):
+ return (
+ f"{escape(name)} set to: "
+ f"{escape(value)}"
+ )
+
+
+def setupLogging(
+ addStreamHandler: bool = True,
+ logFile: Optional[str] = None,
+ name: str = "instrumentserver",
+ streamHandlerLevel: int = logging.INFO,
+) -> None:
"""Setting up logging, including adding a custom handler."""
logger = logging.getLogger(name)
@@ -167,7 +191,7 @@ def setupLogging(addStreamHandler=True, logFile=None,
if logFile is not None:
fmt = logging.Formatter(
"%(asctime)s\t: %(name)s\t: %(levelname)s\t: %(message)s",
- datefmt='%Y-%m-%d %H:%M:%S',
+ datefmt="%Y-%m-%d %H:%M:%S",
)
fh = logging.FileHandler(logFile)
fh.setFormatter(fmt)
@@ -177,7 +201,7 @@ def setupLogging(addStreamHandler=True, logFile=None,
if addStreamHandler:
fmt = logging.Formatter(
"[%(asctime)s] [%(name)s: %(levelname)s] %(message)s",
- datefmt='%m/%d %H:%M',
+ datefmt="%m/%d %H:%M",
)
streamHandler = logging.StreamHandler(sys.stderr)
streamHandler.setFormatter(fmt)
@@ -187,12 +211,12 @@ def setupLogging(addStreamHandler=True, logFile=None,
logger.info(f"Logging set up for {name}.")
-def logger(name='instrumentserver'):
+def logger(name: str = "instrumentserver") -> logging.Logger:
"""Get the (root) logger for the package."""
return logging.getLogger(name)
-def log(logger, message, level):
+def log(logger: logging.Logger, message: str, level: LogLevels) -> None:
"""Simple wrapper to log messages.
Useful when the log level is a variable.
diff --git a/instrumentserver/testing/__init__.py b/src/instrumentserver/monitoring/__init__.py
similarity index 100%
rename from instrumentserver/testing/__init__.py
rename to src/instrumentserver/monitoring/__init__.py
diff --git a/instrumentserver/monitoring/listener.py b/src/instrumentserver/monitoring/listener.py
similarity index 66%
rename from instrumentserver/monitoring/listener.py
rename to src/instrumentserver/monitoring/listener.py
index cd06e23..6e7a26c 100644
--- a/instrumentserver/monitoring/listener.py
+++ b/src/instrumentserver/monitoring/listener.py
@@ -3,16 +3,21 @@
import os.path
from abc import ABC, abstractmethod
from dataclasses import dataclass
-from datetime import datetime, timedelta, timezone
+from datetime import datetime
from pathlib import Path
-from typing import Any, Dict
+from typing import Any, Dict, Optional
+from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
import pandas as pd
-import pytz
-import ruamel.yaml # type: ignore[import-untyped] # Known bugfix under no-fix status: https://sourceforge.net/p/ruamel-yaml/tickets/328/
+import ruamel.yaml
import zmq
+
try:
- from influxdb_client import InfluxDBClient, Point, WriteOptions
+ from influxdb_client import ( # type: ignore[import-not-found]
+ InfluxDBClient,
+ Point,
+ WriteOptions,
+ )
except ImportError:
pass
@@ -22,11 +27,15 @@
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
+
class Listener(ABC):
- def __init__(self, addresses: list):
- self.addresses = addresses
+ def __init__(self, addresses: list) -> None:
+ self.addresses = addresses
+
+ @abstractmethod
+ def listenerEvent(self, *args: Any, **kwargs: Any) -> None: ...
- def run(self):
+ def run(self) -> None:
# creates zmq subscriber at specified address
logger.info(f"Connecting to {self.addresses}")
@@ -49,7 +58,7 @@ def run(self):
try:
# parses string message and decodes into ParameterBroadcastBluePrint
message = recvMultipart(socket)
- self.listenerEvent(message[0],message[1])
+ self.listenerEvent(message[0], message[1])
except (KeyboardInterrupt, SystemExit):
# exit if keyboard interrupt
logger.info("Program Stopped Manually")
@@ -65,39 +74,40 @@ class CSVConfig:
csv_path: str
@classmethod
- def from_dict(cls, config_dict):
+ def from_dict(cls, config_dict: Dict[str, Any]) -> "CSVConfig":
return cls(
- addresses=config_dict['addresses'],
- params=config_dict['params'],
- csv_path=config_dict['csv_path']
+ addresses=config_dict["addresses"],
+ params=config_dict["params"],
+ csv_path=config_dict["csv_path"],
)
-
+
+
@dataclass
class InfluxConfig:
addresses: list
params: list
token: str
org: str
- bucketDict: Dict[str,str]
+ bucketDict: Dict[str, str]
url: str
- measurementNameDict: Dict[str,str]
- timezone_name: str = 'CDT'
+ measurementNameDict: Dict[str, str]
+ timezone_name: str = "CDT"
@classmethod
- def from_dict(cls, config_dict):
+ def from_dict(cls, config_dict: Dict[str, Any]) -> "InfluxConfig":
return cls(
- addresses=config_dict['addresses'],
- params=config_dict['params'],
- token=config_dict['token'],
- org=config_dict['org'],
- bucketDict=config_dict['bucketDict'],
- url=config_dict['url'],
- measurementNameDict=config_dict['measurementNameDict']
+ addresses=config_dict["addresses"],
+ params=config_dict["params"],
+ token=config_dict["token"],
+ org=config_dict["org"],
+ bucketDict=config_dict["bucketDict"],
+ url=config_dict["url"],
+ measurementNameDict=config_dict["measurementNameDict"],
)
class DFListener(Listener):
- def __init__(self, csvConfig: CSVConfig):
+ def __init__(self, csvConfig: CSVConfig) -> None:
super().__init__(csvConfig.addresses)
self.addresses = csvConfig.addresses
self.path = csvConfig.csv_path
@@ -108,28 +118,38 @@ def __init__(self, csvConfig: CSVConfig):
self.df = pd.read_csv(self.path)
self.df = self.df.drop("Unnamed: 0", axis=1)
else:
- self.df = pd.DataFrame(columns=["time","name","value","unit"])
+ self.df = pd.DataFrame(columns=["time", "name", "value", "unit"])
self.paramList = list(csvConfig.params)
- def run(self):
+ def run(self) -> None:
super().run()
- def listenerEvent(self, message: ParameterBroadcastBluePrint):
-
+ def listenerEvent(self, message: ParameterBroadcastBluePrint) -> None:
+
# listens only for parameters in the list, if it is empty, it listens to everything
if not self.paramList:
logger.info(f"Writing data [{message.name},{message.value},{message.unit}]")
- self.df.loc[len(self.df)]=[datetime.now(),message.name,message.value,message.unit]
+ self.df.loc[len(self.df)] = [
+ datetime.now(),
+ message.name,
+ message.value,
+ message.unit,
+ ]
self.df.to_csv(self.path)
elif message.name in self.paramList:
logger.info(f"Writing data [{message.name},{message.value},{message.unit}]")
- self.df.loc[len(self.df)]=[datetime.now(),message.name,message.value,message.unit]
+ self.df.loc[len(self.df)] = [
+ datetime.now(),
+ message.name,
+ message.value,
+ message.unit,
+ ]
self.df.to_csv(self.path)
-class InfluxListener(Listener):
- def __init__(self, influxConfig: InfluxConfig):
+class InfluxListener(Listener):
+ def __init__(self, influxConfig: InfluxConfig) -> None:
super().__init__(influxConfig.addresses)
self.addresses = influxConfig.addresses
@@ -145,10 +165,12 @@ def __init__(self, influxConfig: InfluxConfig):
self.timezone_info = get_timezone_info(influxConfig.timezone_name)
- def run(self):
+ def run(self) -> None:
super().run()
- def listenerEvent(self, instrument, message: ParameterBroadcastBluePrint):
+ def listenerEvent(
+ self, instrument: str, message: ParameterBroadcastBluePrint
+ ) -> None:
bucket = self.bucketDict[instrument]
measurementName = self.measurementNameDict[instrument]
# listens only for parameters in the list, if it is empty, it listens to everything
@@ -163,19 +185,28 @@ def listenerEvent(self, instrument, message: ParameterBroadcastBluePrint):
self.write_api.write(bucket=bucket, org=self.org, record=point)
-def checkInfluxConfig(configInput: Dict[str, Any]):
+def checkInfluxConfig(configInput: Dict[str, Any]) -> bool:
# check if all fields are present in the config file
- influxFields = ['addresses', 'params', 'token', 'org', 'bucketDict', 'url', 'measurementNameDict']
+ influxFields = [
+ "addresses",
+ "params",
+ "token",
+ "org",
+ "bucketDict",
+ "url",
+ "measurementNameDict",
+ ]
for field in influxFields:
if field not in configInput or configInput[field] is None:
logger.info(f"Missing field {field} in config file")
return False
- if 'measurementName' not in configInput or configInput['measurementName'] is None:
- configInput['measurementName'] = 'my_measurement'
+ if "measurementName" not in configInput or configInput["measurementName"] is None:
+ configInput["measurementName"] = "my_measurement"
return True
-def checkCSVConfig(configInput: Dict[str, Any]):
+
+def checkCSVConfig(configInput: Dict[str, Any]) -> bool:
# check if all fields are present in the config file
csvField = ["addresses", "params", "csv_path"]
@@ -185,18 +216,18 @@ def checkCSVConfig(configInput: Dict[str, Any]):
return False
return True
-def get_timezone_info(timezone_name):
+
+def get_timezone_info(timezone_name: str) -> Optional[ZoneInfo]:
try:
- tz = pytz.timezone(timezone_name)
- return tz
- except pytz.UnknownTimeZoneError:
+ return ZoneInfo(timezone_name)
+ except ZoneInfoNotFoundError:
print(f"Unknown timezone: {timezone_name}")
return None
-
-def startListener():
- parser = argparse.ArgumentParser(description='Starting the listener')
+def startListener() -> None:
+
+ parser = argparse.ArgumentParser(description="Starting the listener")
parser.add_argument("-c", "--config")
args = parser.parse_args()
@@ -204,23 +235,23 @@ def startListener():
yaml = ruamel.yaml.YAML()
# load variables from config file
- if configPath != '' and configPath is not None:
+ if configPath != "" and configPath is not None:
configInput = yaml.load(configPath)
else:
logger.warning("Please enter a valid path for the config file")
- return 0
+ return
# start listener that writes to CSV or Influx Database
- if 'type' in configInput:
- if configInput['type'] == "CSV":
+ if "type" in configInput:
+ if configInput["type"] == "CSV":
if checkCSVConfig(configInput):
CSVListener = DFListener(CSVConfig.from_dict(configInput))
CSVListener.run()
- elif configInput['type'] == "Influx":
+ elif configInput["type"] == "Influx":
if checkInfluxConfig(configInput):
DBListener = InfluxListener(InfluxConfig.from_dict(configInput))
DBListener.run()
else:
logger.warning(f"Type {configInput['type']} not supported")
else:
- logger.warning("Please enter a valid type in the config file")
\ No newline at end of file
+ logger.warning("Please enter a valid type in the config file")
diff --git a/instrumentserver/params.py b/src/instrumentserver/params.py
similarity index 72%
rename from instrumentserver/params.py
rename to src/instrumentserver/params.py
index 07de93f..41d204c 100644
--- a/instrumentserver/params.py
+++ b/src/instrumentserver/params.py
@@ -1,10 +1,10 @@
+import json
+import logging
import os
+from enum import Enum, auto, unique
from pathlib import Path
-from typing import Any, Dict, Union, List
-from enum import Enum, unique, auto
-import logging
+from typing import Any, Dict, List, Union
-import json
from qcodes import Parameter
from qcodes.instrument.base import InstrumentBase
from qcodes.parameters import ParameterBase
@@ -12,7 +12,6 @@
from . import serialize
-
logger = logging.getLogger(__name__)
@@ -27,24 +26,15 @@ class ParameterTypes(Enum):
parameterTypes = {
- ParameterTypes.any:
- {'name': 'Any',
- 'validatorType': validators.Anything},
- ParameterTypes.numeric:
- {'name': 'Numeric',
- 'validatorType': validators.Numbers},
- ParameterTypes.integer:
- {'name': 'Integer',
- 'validatorType': validators.Ints},
- ParameterTypes.string:
- {'name': 'String',
- 'validatorType': validators.Strings},
- ParameterTypes.bool:
- {'name': 'Boolean',
- 'validatorType': validators.Bool},
- ParameterTypes.complex:
- {'name': 'Complex',
- 'validatorType': validators.ComplexNumbers},
+ ParameterTypes.any: {"name": "Any", "validatorType": validators.Anything},
+ ParameterTypes.numeric: {"name": "Numeric", "validatorType": validators.Numbers},
+ ParameterTypes.integer: {"name": "Integer", "validatorType": validators.Ints},
+ ParameterTypes.string: {"name": "String", "validatorType": validators.Strings},
+ ParameterTypes.bool: {"name": "Boolean", "validatorType": validators.Bool},
+ ParameterTypes.complex: {
+ "name": "Complex",
+ "validatorType": validators.ComplexNumbers,
+ },
}
@@ -53,7 +43,7 @@ def paramTypeFromVals(vals: validators.Validator | None) -> Union[ParameterTypes
vals = validators.Anything()
for k, v in parameterTypes.items():
- validator_type = v['validatorType']
+ validator_type = v["validatorType"]
if isinstance(validator_type, type) and isinstance(vals, validator_type):
return k
@@ -62,7 +52,7 @@ def paramTypeFromVals(vals: validators.Validator | None) -> Union[ParameterTypes
def paramTypeFromName(name: str) -> Union[ParameterTypes, None]:
for k, v in parameterTypes.items():
- if name == v['name']:
+ if name == v["name"]:
return k
return None
@@ -82,28 +72,28 @@ class ParameterManager(InstrumentBase):
# TODO: method to instantiate entirely from paramDict
- def __init__(self, name):
+ def __init__(self, name: str) -> None:
super().__init__(name)
self._workingDirectory = Path(os.getcwd())
#: default location and name of the parameters save file.
self.selectedProfile = self.fullProfileName(self.name)
- self.profiles = []
+ self.profiles: List[str] = []
self.refresh_profiles()
self.fromFile()
@property
- def workingDirectory(self):
+ def workingDirectory(self) -> Path:
return self._workingDirectory
@workingDirectory.setter
- def workingDirectory(self, path: Union[str, Path]):
+ def workingDirectory(self, path: Union[str, Path]) -> None:
self._workingDirectory = Path(path)
self.refresh_profiles()
- def getWorkingDirectory(self):
+ def getWorkingDirectory(self): # type: ignore[no-untyped-def]
return self.workingDirectory
@staticmethod
@@ -123,7 +113,7 @@ def cleanProfileName(name: str) -> str:
When passed the full file name of a parameter_manager profile, return only the middle
string representing the profile's name.
"""
- return name.replace('parameter_manager-', '').replace('.json', '')
+ return name.replace("parameter_manager-", "").replace(".json", "")
@staticmethod
def fullProfileName(name: str) -> str:
@@ -131,14 +121,14 @@ def fullProfileName(name: str) -> str:
Adds 'parameter_manager-' to the beginning of `name` and adds '.json' at the end.
"""
- if not name.startswith('parameter_manager-'):
- name = 'parameter_manager-' + name
- if not name.endswith('.json'):
- name += '.json'
+ if not name.startswith("parameter_manager-"):
+ name = "parameter_manager-" + name
+ if not name.endswith(".json"):
+ name += ".json"
return name
@classmethod
- def _to_tree(cls, pm: 'ParameterManager') -> Dict:
+ def _to_tree(cls, pm: "ParameterManager") -> Dict:
ret: dict[str, Any] = {}
for smn, sm in pm.submodules.items():
assert isinstance(sm, ParameterManager)
@@ -148,7 +138,7 @@ def _to_tree(cls, pm: 'ParameterManager') -> Dict:
return ret
@classmethod
- def does_profile_exist(cls, profiles, target):
+ def does_profile_exist(cls, profiles: List[str], target: str) -> bool:
found = False
for profile in profiles:
if target in profile:
@@ -170,44 +160,47 @@ def refresh_profiles(self) -> List[str]:
self.profiles = profiles
return profiles
- def to_tree(self):
+ def to_tree(self) -> Dict:
return ParameterManager._to_tree(self)
def _get_param(self, param_name: str) -> ParameterBase:
parent = self._get_parent(param_name)
try:
- param = parent.parameters[param_name.split('.')[-1]]
+ param = parent.parameters[param_name.split(".")[-1]]
return param
except KeyError:
raise ValueError(f"Parameter '{param_name}' does not exist")
- def _get_parent(self, param_name: str, create_parent: bool = False) \
- -> 'ParameterManager':
+ def _get_parent(
+ self, param_name: str, create_parent: bool = False
+ ) -> "ParameterManager":
- split_names = param_name.split('.')
+ split_names = param_name.split(".")
parent = self
full_name = self.name
for i, n in enumerate(split_names[:-1]):
- full_name += f'.{n}'
+ full_name += f".{n}"
if n in parent.parameters:
- raise ValueError(f"{n} is a parameter, and cannot have child parameters.")
+ raise ValueError(
+ f"{n} is a parameter, and cannot have child parameters."
+ )
if n not in parent.submodules:
if create_parent:
- parent.add_submodule(n, ParameterManager(n)) # type: ignore # This one is technically breaking the type hints from qcodes itself, but it seems to work fine.
+ parent.add_submodule(n, ParameterManager(n)) # type: ignore[type-var]
else:
- raise ValueError(f'{n} does not exist.')
- parent = parent.submodules[n] # type: ignore # This one is technically breaking the type hints from qcodes itself, but it seems to work fine.
+ raise ValueError(f"{n} does not exist.")
+ parent = parent.submodules[n] # type: ignore[assignment]
return parent
- def has_param(self, param_name: str):
+ def has_param(self, param_name: str) -> bool:
try:
- param = self._get_param(param_name)
+ self._get_param(param_name)
return True
except ValueError:
return False
- def add_parameter(self, name: str, **kw: Any) -> None: # type: ignore # Breaks LSP principle, code works, don't want to change it.
+ def add_parameter(self, name: str, **kw: Any) -> None: # type: ignore[override]
"""Add a parameter.
:param name: Name of the parameter.
@@ -223,20 +216,20 @@ def add_parameter(self, name: str, **kw: Any) -> None: # type: ignore # Breaks
- ``vals`` defaults to ``qcodes.utils.validators.Anything()``.
:return: None.
"""
- kw['parameter_class'] = Parameter
- if 'vals' not in kw:
- kw['vals'] = validators.Anything()
- kw['set_cmd'] = None
+ kw["parameter_class"] = Parameter
+ if "vals" not in kw:
+ kw["vals"] = validators.Anything()
+ kw["set_cmd"] = None
parent = self._get_parent(name, create_parent=True)
if parent is self:
- super().add_parameter(name.split('.')[-1], **kw)
+ super().add_parameter(name.split(".")[-1], **kw)
else:
- parent.add_parameter(name.split('.')[-1], **kw)
+ parent.add_parameter(name.split(".")[-1], **kw)
- def remove_parameter(self, param_name: str, cleanup: bool = True):
+ def remove_parameter(self, param_name: str, cleanup: bool = True) -> None:
parent = self._get_parent(param_name)
- pname = param_name.split('.')[-1]
+ pname = param_name.split(".")[-1]
del parent.parameters[pname]
if cleanup:
self.remove_empty_submodules()
@@ -249,27 +242,27 @@ def set(self, param_name: str, value: Any) -> Any:
param = self._get_param(param_name)
param.set(value)
- def remove_empty_submodules(self):
+ def remove_empty_submodules(self) -> None:
"""Delete all empty submodules in the instrument."""
- def is_empty(parent):
+ def is_empty(parent: InstrumentBase) -> bool:
if len(parent.submodules) == 0 and len(parent.parameters) == 0:
return True
else:
return False
- def purge(parent):
+ def purge(parent: InstrumentBase) -> None:
mark_for_deletion = []
for n, s in parent.submodules.items():
- purge(s)
- if is_empty(s):
+ purge(s) # type: ignore[arg-type]
+ if is_empty(s): # type: ignore[arg-type]
mark_for_deletion.append(n)
for n in mark_for_deletion:
del parent.submodules[n]
purge(self)
- def remove_all_parameters(self):
+ def remove_all_parameters(self) -> None:
"""Remove all parameters from the instrument."""
for param in self.list():
self.remove_parameter(param, cleanup=False)
@@ -287,7 +280,7 @@ def list(self) -> List[str]:
"""Return a list of all parameters."""
tree = self.to_tree()
- def tolist(x):
+ def tolist(x: Dict[str, Any]) -> List[str]:
ret_ = []
for k, v in x.items():
if isinstance(v, Parameter):
@@ -298,7 +291,11 @@ def tolist(x):
return tolist(tree)
- def fromFile(self, filePath: str | None = None, deleteMissing: bool = True):
+ def fromFile(
+ self,
+ filePath: str | None = None,
+ deleteMissing: bool = True,
+ ) -> None:
"""Load parameters from a parameter json file
(see :mod:`.serialize`).
@@ -311,16 +308,22 @@ def fromFile(self, filePath: str | None = None, deleteMissing: bool = True):
ParameterManager that are not listed in the file.
"""
if filePath is None:
- filePath = self.workingDirectory.joinpath(self.fullProfileName(self.selectedProfile))
+ filePath = str(
+ self.workingDirectory.joinpath(
+ self.fullProfileName(self.selectedProfile)
+ )
+ )
if os.path.exists(filePath):
- with open(filePath, 'r') as f:
+ with open(filePath, "r") as f:
pd = json.load(f)
self.fromParamDict(pd)
path = Path(filePath)
- if path.name.startswith("parameter_manager-") and path.name.endswith(".json"):
+ if path.name.startswith("parameter_manager-") and path.name.endswith(
+ ".json"
+ ):
profileName = path.name
self.selectedProfile = profileName
if path.name not in self.profiles:
@@ -329,8 +332,9 @@ def fromFile(self, filePath: str | None = None, deleteMissing: bool = True):
else:
logger.warning("parameter file not found, cannot load.")
- def fromParamDict(self, paramDict: Dict[str, Any],
- deleteMissing: bool = True):
+ def fromParamDict(
+ self, paramDict: Dict[str, Any], deleteMissing: bool = True
+ ) -> None:
"""Load parameters from a parameter dictionary (see :mod:`.serialize`).
:param paramDict: Parameter dictionary.
@@ -344,16 +348,19 @@ def fromParamDict(self, paramDict: Dict[str, Any],
simple = False
currentParams = self.list()
- fileParams = ['.'.join(k.split('.')[1:]) for k in paramDict.keys()
- if k.split('.')[0] == self.name]
+ fileParams = [
+ ".".join(k.split(".")[1:])
+ for k in paramDict.keys()
+ if k.split(".")[0] == self.name
+ ]
for pn in fileParams:
if simple:
val = paramDict[f"{self.name}.{pn}"]
- unit = ''
+ unit = ""
else:
- val = paramDict[f"{self.name}.{pn}"]['value']
- unit = paramDict[f"{self.name}.{pn}"].get('unit', '')
+ val = paramDict[f"{self.name}.{pn}"]["value"]
+ unit = paramDict[f"{self.name}.{pn}"].get("unit", "")
if self.has_param(pn):
self.parameter(pn)(val)
@@ -369,13 +376,19 @@ def fromParamDict(self, paramDict: Dict[str, Any],
if pn not in fileParams and deleteMissing:
self.remove_parameter(pn)
- def toParamDict(self, simpleFormat: bool = False, includeMeta: List[str] = ['unit']):
- params = serialize.toParamDict([self], simpleFormat=simpleFormat,
- includeMeta=includeMeta)
+ def toParamDict(
+ self, simpleFormat: bool = False, includeMeta: List[str] = ["unit"]
+ ) -> Dict[str, Any]:
+ params = serialize.toParamDict(
+ [self], simpleFormat=simpleFormat, includeMeta=includeMeta
+ )
return params
- def toFile(self, filePath: str | None = None, name: str | None = None):
-
+ def toFile(
+ self,
+ filePath: str | None = None,
+ name: str | None = None,
+ ) -> None:
"""Save parameters from the instrument into a json file.
If the file being saved is a profile file (starts with 'parameter_manager-' and ends with '.json'),
the selectedProfile is changed to the filename.
@@ -390,18 +403,18 @@ def toFile(self, filePath: str | None = None, name: str | None = None):
"""
if filePath is None:
- filePath = self.workingDirectory
+ filePath = str(self.workingDirectory)
if os.path.isdir(filePath):
if name is None:
name = self.selectedProfile
- filePath = os.path.join(filePath, self.fullProfileName(name))
+ filePath = os.path.join(filePath, self.fullProfileName(name))
folder, file = os.path.split(filePath)
params = self.toParamDict()
if not os.path.exists(folder):
os.makedirs(folder)
- with open(filePath, 'w') as f:
+ with open(filePath, "w") as f:
json.dump(params, f, indent=2, sort_keys=True)
file = str(file)
@@ -414,7 +427,7 @@ def list_profiles(self) -> List[str]:
"""
return self.profiles
- def switch_to_profile(self, profile: str):
+ def switch_to_profile(self, profile: str) -> None:
"""
Switches the server to the passed profile.
"""
@@ -423,5 +436,7 @@ def switch_to_profile(self, profile: str):
self.toFile(str(self.workingDirectory), self.selectedProfile)
self.remove_all_parameters()
- self.fromFile(str(self.workingDirectory.joinpath(self.fullProfileName(profile))))
+ self.fromFile(
+ str(self.workingDirectory.joinpath(self.fullProfileName(profile)))
+ )
self.selectedProfile = self.fullProfileName(profile)
diff --git a/instrumentserver/resource.py b/src/instrumentserver/resource.py
similarity index 98%
rename from instrumentserver/resource.py
rename to src/instrumentserver/resource.py
index b6611bd..d4fd9e9 100644
--- a/instrumentserver/resource.py
+++ b/src/instrumentserver/resource.py
@@ -1,1473 +1,1480 @@
-# -*- coding: utf-8 -*-
-
-# Resource object code
-#
-# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
-#
-# WARNING! All changes made in this file will be lost!
-
-from PyQt5 import QtCore
-
-qt_resource_data = b"\
-\x00\x00\x00\x00\
-\
-\x00\x00\x01\x75\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x70\x6c\x75\x73\x2d\
-\x73\x71\x75\x61\x72\x65\x22\x3e\x3c\x72\x65\x63\x74\x20\x78\x3d\
-\x22\x33\x22\x20\x79\x3d\x22\x33\x22\x20\x77\x69\x64\x74\x68\x3d\
-\x22\x31\x38\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x31\x38\x22\
-\x20\x72\x78\x3d\x22\x32\x22\x20\x72\x79\x3d\x22\x32\x22\x3e\x3c\
-\x2f\x72\x65\x63\x74\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\x22\
-\x31\x32\x22\x20\x79\x31\x3d\x22\x38\x22\x20\x78\x32\x3d\x22\x31\
-\x32\x22\x20\x79\x32\x3d\x22\x31\x36\x22\x3e\x3c\x2f\x6c\x69\x6e\
-\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\x22\x38\x22\x20\x79\
-\x31\x3d\x22\x31\x32\x22\x20\x78\x32\x3d\x22\x31\x36\x22\x20\x79\
-\x32\x3d\x22\x31\x32\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x2f\
-\x73\x76\x67\x3e\
-\x00\x00\x01\xa0\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x61\x6c\x65\x72\x74\
-\x2d\x6f\x63\x74\x61\x67\x6f\x6e\x22\x3e\x3c\x70\x6f\x6c\x79\x67\
-\x6f\x6e\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x2e\x38\x36\x20\
-\x32\x20\x31\x36\x2e\x31\x34\x20\x32\x20\x32\x32\x20\x37\x2e\x38\
-\x36\x20\x32\x32\x20\x31\x36\x2e\x31\x34\x20\x31\x36\x2e\x31\x34\
-\x20\x32\x32\x20\x37\x2e\x38\x36\x20\x32\x32\x20\x32\x20\x31\x36\
-\x2e\x31\x34\x20\x32\x20\x37\x2e\x38\x36\x20\x37\x2e\x38\x36\x20\
-\x32\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x67\x6f\x6e\x3e\x3c\x6c\x69\
-\x6e\x65\x20\x78\x31\x3d\x22\x31\x32\x22\x20\x79\x31\x3d\x22\x38\
-\x22\x20\x78\x32\x3d\x22\x31\x32\x22\x20\x79\x32\x3d\x22\x31\x32\
-\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\
-\x31\x3d\x22\x31\x32\x22\x20\x79\x31\x3d\x22\x31\x36\x22\x20\x78\
-\x32\x3d\x22\x31\x32\x2e\x30\x31\x22\x20\x79\x32\x3d\x22\x31\x36\
-\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x07\xd9\
-\x3c\
-\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
-\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x78\x6d\x6c\x6e\x73\
-\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
-\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x66\x69\
-\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x3e\x0a\x20\x3c\x67\x3e\x0a\
-\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x4c\x61\x79\x65\x72\x20\x31\
-\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
-\x20\x64\x3d\x22\x6d\x33\x38\x2e\x31\x34\x34\x31\x2c\x31\x35\x2e\
-\x31\x36\x31\x36\x31\x63\x30\x2e\x36\x37\x31\x38\x2c\x2d\x31\x2e\
-\x36\x37\x32\x39\x20\x33\x2e\x30\x34\x2c\x2d\x31\x2e\x36\x37\x32\
-\x39\x20\x33\x2e\x37\x31\x31\x38\x2c\x30\x6c\x35\x2e\x39\x35\x32\
-\x32\x2c\x31\x34\x2e\x38\x32\x32\x32\x63\x30\x2e\x32\x38\x36\x31\
-\x2c\x30\x2e\x37\x31\x32\x34\x20\x30\x2e\x39\x35\x34\x37\x2c\x31\
-\x2e\x31\x39\x38\x31\x20\x31\x2e\x37\x32\x30\x37\x2c\x31\x2e\x32\
-\x35\x30\x31\x6c\x31\x35\x2e\x39\x33\x36\x2c\x31\x2e\x30\x38\x30\
-\x35\x63\x31\x2e\x37\x39\x38\x37\x2c\x30\x2e\x31\x32\x32\x20\x32\
-\x2e\x35\x33\x30\x35\x2c\x32\x2e\x33\x37\x34\x34\x20\x31\x2e\x31\
-\x34\x37\x2c\x33\x2e\x35\x33\x30\x32\x6c\x2d\x31\x32\x2e\x32\x35\
-\x37\x34\x2c\x31\x30\x2e\x32\x34\x31\x32\x63\x2d\x30\x2e\x35\x38\
-\x39\x31\x2c\x30\x2e\x34\x39\x32\x32\x20\x2d\x30\x2e\x38\x34\x34\
-\x35\x2c\x31\x2e\x32\x37\x38\x32\x20\x2d\x30\x2e\x36\x35\x37\x32\
-\x2c\x32\x2e\x30\x32\x32\x37\x6c\x33\x2e\x38\x39\x36\x39\x2c\x31\
-\x35\x2e\x34\x39\x63\x30\x2e\x34\x33\x39\x38\x2c\x31\x2e\x37\x34\
-\x38\x33\x20\x2d\x31\x2e\x34\x37\x36\x32\x2c\x33\x2e\x31\x34\x30\
-\x34\x20\x2d\x33\x2e\x30\x30\x33\x2c\x32\x2e\x31\x38\x31\x38\x6c\
-\x2d\x31\x33\x2e\x35\x32\x37\x37\x2c\x2d\x38\x2e\x34\x39\x32\x38\
-\x63\x2d\x30\x2e\x36\x35\x30\x32\x2c\x2d\x30\x2e\x34\x30\x38\x32\
-\x20\x2d\x31\x2e\x34\x37\x36\x36\x2c\x2d\x30\x2e\x34\x30\x38\x32\
-\x20\x2d\x32\x2e\x31\x32\x36\x38\x2c\x30\x6c\x2d\x31\x33\x2e\x35\
-\x32\x37\x37\x2c\x38\x2e\x34\x39\x32\x38\x63\x2d\x31\x2e\x35\x32\
-\x36\x38\x2c\x30\x2e\x39\x35\x38\x36\x20\x2d\x33\x2e\x34\x34\x32\
-\x38\x2c\x2d\x30\x2e\x34\x33\x33\x35\x20\x2d\x33\x2e\x30\x30\x33\
-\x2c\x2d\x32\x2e\x31\x38\x31\x38\x6c\x33\x2e\x38\x39\x36\x39\x2c\
-\x2d\x31\x35\x2e\x34\x39\x63\x30\x2e\x31\x38\x37\x33\x2c\x2d\x30\
-\x2e\x37\x34\x34\x35\x20\x2d\x30\x2e\x30\x36\x38\x31\x2c\x2d\x31\
-\x2e\x35\x33\x30\x35\x20\x2d\x30\x2e\x36\x35\x37\x32\x2c\x2d\x32\
-\x2e\x30\x32\x32\x37\x6c\x2d\x31\x32\x2e\x32\x35\x37\x34\x2c\x2d\
-\x31\x30\x2e\x32\x34\x31\x32\x63\x2d\x31\x2e\x33\x38\x33\x35\x2c\
-\x2d\x31\x2e\x31\x35\x35\x38\x20\x2d\x30\x2e\x36\x35\x31\x37\x2c\
-\x2d\x33\x2e\x34\x30\x38\x32\x20\x31\x2e\x31\x34\x37\x2c\x2d\x33\
-\x2e\x35\x33\x30\x32\x6c\x31\x35\x2e\x39\x33\x36\x2c\x2d\x31\x2e\
-\x30\x38\x30\x35\x63\x30\x2e\x37\x36\x36\x2c\x2d\x30\x2e\x30\x35\
-\x32\x20\x31\x2e\x34\x33\x34\x36\x2c\x2d\x30\x2e\x35\x33\x37\x37\
-\x20\x31\x2e\x37\x32\x30\x37\x2c\x2d\x31\x2e\x32\x35\x30\x31\x6c\
-\x35\x2e\x39\x35\x32\x32\x2c\x2d\x31\x34\x2e\x38\x32\x32\x32\x7a\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\x39\x39\x34\x41\x22\
-\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x31\x22\x2f\x3e\x0a\x20\x20\
-\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x6d\x33\x39\x2e\x30\x35\x31\
-\x35\x2c\x32\x36\x2e\x33\x31\x30\x38\x63\x30\x2e\x33\x33\x35\x39\
-\x2c\x2d\x30\x2e\x38\x33\x36\x34\x20\x31\x2e\x35\x32\x30\x31\x2c\
-\x2d\x30\x2e\x38\x33\x36\x34\x20\x31\x2e\x38\x35\x36\x2c\x30\x6c\
-\x32\x2e\x39\x37\x36\x2c\x37\x2e\x34\x31\x31\x31\x63\x30\x2e\x31\
-\x34\x33\x31\x2c\x30\x2e\x33\x35\x36\x32\x20\x30\x2e\x34\x37\x37\
-\x34\x2c\x30\x2e\x35\x39\x39\x31\x20\x30\x2e\x38\x36\x30\x34\x2c\
-\x30\x2e\x36\x32\x35\x31\x6c\x37\x2e\x39\x36\x38\x2c\x30\x2e\x35\
-\x34\x30\x33\x63\x30\x2e\x38\x39\x39\x33\x2c\x30\x2e\x30\x36\x30\
-\x39\x20\x31\x2e\x32\x36\x35\x32\x2c\x31\x2e\x31\x38\x37\x31\x20\
-\x30\x2e\x35\x37\x33\x35\x2c\x31\x2e\x37\x36\x35\x31\x6c\x2d\x36\
-\x2e\x31\x32\x38\x37\x2c\x35\x2e\x31\x32\x30\x35\x63\x2d\x30\x2e\
-\x32\x39\x34\x36\x2c\x30\x2e\x32\x34\x36\x32\x20\x2d\x30\x2e\x34\
-\x32\x32\x33\x2c\x30\x2e\x36\x33\x39\x32\x20\x2d\x30\x2e\x33\x32\
-\x38\x36\x2c\x31\x2e\x30\x31\x31\x34\x6c\x31\x2e\x39\x34\x38\x34\
-\x2c\x37\x2e\x37\x34\x35\x63\x30\x2e\x32\x31\x39\x39\x2c\x30\x2e\
-\x38\x37\x34\x32\x20\x2d\x30\x2e\x37\x33\x38\x31\x2c\x31\x2e\x35\
-\x37\x30\x32\x20\x2d\x31\x2e\x35\x30\x31\x35\x2c\x31\x2e\x30\x39\
-\x30\x39\x6c\x2d\x36\x2e\x37\x36\x33\x38\x2c\x2d\x34\x2e\x32\x34\
-\x36\x34\x63\x2d\x30\x2e\x33\x32\x35\x31\x2c\x2d\x30\x2e\x32\x30\
-\x34\x31\x20\x2d\x30\x2e\x37\x33\x38\x33\x2c\x2d\x30\x2e\x32\x30\
-\x34\x31\x20\x2d\x31\x2e\x30\x36\x33\x34\x2c\x30\x6c\x2d\x36\x2e\
-\x37\x36\x33\x38\x2c\x34\x2e\x32\x34\x36\x34\x63\x2d\x30\x2e\x37\
-\x36\x33\x35\x2c\x30\x2e\x34\x37\x39\x33\x20\x2d\x31\x2e\x37\x32\
-\x31\x34\x2c\x2d\x30\x2e\x32\x31\x36\x37\x20\x2d\x31\x2e\x35\x30\
-\x31\x35\x2c\x2d\x31\x2e\x30\x39\x30\x39\x6c\x31\x2e\x39\x34\x38\
-\x34\x2c\x2d\x37\x2e\x37\x34\x35\x63\x30\x2e\x30\x39\x33\x36\x2c\
-\x2d\x30\x2e\x33\x37\x32\x32\x20\x2d\x30\x2e\x30\x33\x34\x31\x2c\
-\x2d\x30\x2e\x37\x36\x35\x32\x20\x2d\x30\x2e\x33\x32\x38\x36\x2c\
-\x2d\x31\x2e\x30\x31\x31\x34\x6c\x2d\x36\x2e\x31\x32\x38\x37\x2c\
-\x2d\x35\x2e\x31\x32\x30\x35\x63\x2d\x30\x2e\x36\x39\x31\x38\x2c\
-\x2d\x30\x2e\x35\x37\x38\x20\x2d\x30\x2e\x33\x32\x35\x38\x2c\x2d\
-\x31\x2e\x37\x30\x34\x32\x20\x30\x2e\x35\x37\x33\x35\x2c\x2d\x31\
-\x2e\x37\x36\x35\x31\x6c\x37\x2e\x39\x36\x38\x2c\x2d\x30\x2e\x35\
-\x34\x30\x33\x63\x30\x2e\x33\x38\x33\x2c\x2d\x30\x2e\x30\x32\x36\
-\x20\x30\x2e\x37\x31\x37\x33\x2c\x2d\x30\x2e\x32\x36\x38\x39\x20\
-\x30\x2e\x38\x36\x30\x33\x2c\x2d\x30\x2e\x36\x32\x35\x31\x6c\x32\
-\x2e\x39\x37\x36\x31\x2c\x2d\x37\x2e\x34\x31\x31\x31\x7a\x22\x20\
-\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\x43\x39\x34\x43\x22\x20\x69\
-\x64\x3d\x22\x73\x76\x67\x5f\x32\x22\x2f\x3e\x0a\x20\x20\x3c\x70\
-\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x66\x66\x30\x30\x30\
-\x30\x22\x20\x64\x3d\x22\x6d\x35\x2c\x33\x39\x2e\x39\x39\x39\x39\
-\x34\x6c\x30\x2c\x30\x63\x30\x2c\x2d\x31\x38\x2e\x31\x36\x32\x36\
-\x38\x20\x31\x35\x2e\x36\x37\x30\x30\x35\x2c\x2d\x33\x32\x2e\x38\
-\x38\x36\x35\x34\x20\x33\x34\x2e\x39\x39\x39\x39\x37\x2c\x2d\x33\
-\x32\x2e\x38\x38\x36\x35\x34\x6c\x30\x2c\x30\x63\x39\x2e\x32\x38\
-\x32\x37\x2c\x30\x20\x31\x38\x2e\x31\x38\x35\x31\x37\x2c\x33\x2e\
-\x34\x36\x34\x38\x33\x20\x32\x34\x2e\x37\x34\x38\x37\x34\x2c\x39\
-\x2e\x36\x33\x32\x32\x38\x63\x36\x2e\x35\x36\x33\x38\x33\x2c\x36\
-\x2e\x31\x36\x37\x34\x35\x20\x31\x30\x2e\x32\x35\x31\x32\x39\x2c\
-\x31\x34\x2e\x35\x33\x32\x33\x32\x20\x31\x30\x2e\x32\x35\x31\x32\
-\x39\x2c\x32\x33\x2e\x32\x35\x34\x32\x36\x6c\x30\x2c\x30\x63\x30\
-\x2c\x31\x38\x2e\x31\x36\x32\x39\x20\x2d\x31\x35\x2e\x36\x36\x39\
-\x39\x34\x2c\x33\x32\x2e\x38\x38\x36\x36\x36\x20\x2d\x33\x35\x2e\
-\x30\x30\x30\x30\x33\x2c\x33\x32\x2e\x38\x38\x36\x36\x36\x6c\x30\
-\x2c\x30\x63\x2d\x31\x39\x2e\x33\x32\x39\x39\x32\x2c\x30\x20\x2d\
-\x33\x34\x2e\x39\x39\x39\x39\x37\x2c\x2d\x31\x34\x2e\x37\x32\x33\
-\x37\x36\x20\x2d\x33\x34\x2e\x39\x39\x39\x39\x37\x2c\x2d\x33\x32\
-\x2e\x38\x38\x36\x36\x36\x6c\x30\x2c\x30\x7a\x6d\x35\x36\x2e\x35\
-\x31\x37\x30\x33\x2c\x31\x34\x2e\x37\x31\x31\x38\x6c\x30\x2c\x30\
-\x63\x37\x2e\x37\x30\x35\x38\x34\x2c\x2d\x39\x2e\x39\x35\x30\x37\
-\x31\x20\x36\x2e\x35\x36\x30\x33\x39\x2c\x2d\x32\x33\x2e\x36\x39\
-\x30\x33\x36\x20\x2d\x32\x2e\x37\x30\x30\x35\x37\x2c\x2d\x33\x32\
-\x2e\x33\x39\x32\x63\x2d\x39\x2e\x32\x36\x30\x39\x36\x2c\x2d\x38\
-\x2e\x37\x30\x31\x37\x31\x20\x2d\x32\x33\x2e\x38\x38\x33\x35\x35\
-\x2c\x2d\x39\x2e\x37\x37\x38\x20\x2d\x33\x34\x2e\x34\x37\x33\x34\
-\x35\x2c\x2d\x32\x2e\x35\x33\x37\x33\x38\x6c\x33\x37\x2e\x31\x37\
-\x34\x30\x32\x2c\x33\x34\x2e\x39\x32\x39\x33\x38\x7a\x6d\x2d\x34\
-\x33\x2e\x30\x33\x33\x39\x33\x2c\x2d\x32\x39\x2e\x34\x32\x33\x33\
-\x63\x2d\x37\x2e\x37\x30\x35\x39\x2c\x39\x2e\x39\x35\x30\x36\x36\
-\x20\x2d\x36\x2e\x35\x36\x30\x34\x37\x2c\x32\x33\x2e\x36\x39\x30\
-\x33\x20\x32\x2e\x37\x30\x30\x34\x34\x2c\x33\x32\x2e\x33\x39\x31\
-\x38\x32\x63\x39\x2e\x32\x36\x30\x38\x38\x2c\x38\x2e\x37\x30\x31\
-\x37\x36\x20\x32\x33\x2e\x38\x38\x33\x34\x37\x2c\x39\x2e\x37\x37\
-\x38\x30\x35\x20\x33\x34\x2e\x34\x37\x33\x33\x37\x2c\x32\x2e\x35\
-\x33\x37\x35\x6c\x2d\x33\x37\x2e\x31\x37\x33\x38\x31\x2c\x2d\x33\
-\x34\x2e\x39\x32\x39\x33\x32\x6c\x30\x2c\x30\x7a\x22\x20\x69\x64\
-\x3d\x22\x73\x76\x67\x5f\x34\x22\x2f\x3e\x0a\x20\x3c\x2f\x67\x3e\
-\x0a\x0a\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x01\x6c\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x73\x68\x61\x72\x65\
-\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x34\x20\x31\x32\
-\x76\x38\x61\x32\x20\x32\x20\x30\x20\x30\x20\x30\x20\x32\x20\x32\
-\x68\x31\x32\x61\x32\x20\x32\x20\x30\x20\x30\x20\x30\x20\x32\x2d\
-\x32\x76\x2d\x38\x22\x3e\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x70\x6f\
-\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\
-\x36\x20\x36\x20\x31\x32\x20\x32\x20\x38\x20\x36\x22\x3e\x3c\x2f\
-\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\
-\x31\x3d\x22\x31\x32\x22\x20\x79\x31\x3d\x22\x32\x22\x20\x78\x32\
-\x3d\x22\x31\x32\x22\x20\x79\x32\x3d\x22\x31\x35\x22\x3e\x3c\x2f\
-\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x01\x33\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x63\x6f\x64\x65\x22\
-\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\
-\x73\x3d\x22\x31\x36\x20\x31\x38\x20\x32\x32\x20\x31\x32\x20\x31\
-\x36\x20\x36\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\
-\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\x73\
-\x3d\x22\x38\x20\x36\x20\x32\x20\x31\x32\x20\x38\x20\x31\x38\x22\
-\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\
-\x67\x3e\
-\x00\x00\x03\xa8\
-\x00\
-\x00\x0c\x99\x78\x9c\xdd\x56\x4b\x6f\xe3\x36\x10\xbe\xe7\x57\x08\
-\xca\xa5\x45\x23\x8a\xd4\xcb\xa2\x62\x79\x81\x36\x58\xb4\x87\x5e\
-\xba\xbb\xe8\x99\x21\x69\x5b\x1b\x89\x34\x28\x3a\xb6\xf7\xd7\xef\
-\x50\x0f\x5b\x76\x9c\xf4\x71\x28\xd0\x08\x36\xec\x79\x71\x66\xbe\
-\xf9\x38\xf6\xfc\xc3\xbe\xa9\xbd\x67\x69\xda\x4a\xab\xd2\x27\x08\
-\xfb\x9e\x54\x5c\x8b\x4a\xad\x4a\xff\xcb\xe7\x8f\x41\xee\x7b\xad\
-\x65\x4a\xb0\x5a\x2b\x59\xfa\x4a\xfb\x1f\x16\x37\xf3\xf6\x79\x75\
-\xe3\x79\x1e\x04\xab\xb6\x10\xbc\xf4\xd7\xd6\x6e\x8a\x30\xdc\x6c\
-\x4d\x8d\xb4\x59\x85\x82\x87\xb2\x96\x8d\x54\xb6\x0d\x09\x22\xa1\
-\x7f\x72\xe7\x27\x77\x6e\x24\xb3\xd5\xb3\xe4\xba\x69\xb4\x6a\xbb\
-\x48\xd5\xde\x4e\x9c\x8d\x58\x1e\xbd\x77\xbb\x1d\xda\xc5\x9d\x13\
-\xa1\x94\x86\x38\x0a\xa3\x28\x00\x8f\xa0\x3d\x28\xcb\xf6\xc1\x79\
-\x28\xd4\x78\x2d\x34\xc2\x18\x87\x60\x3b\x79\xfe\x3d\xaf\xa2\x05\
-\x54\x36\xf0\x3e\xba\x8f\x0a\xd4\xea\xad\xe1\x72\x09\x71\x12\x29\
-\x69\xc3\x87\xcf\x0f\x47\x63\x80\x91\xb0\x62\x72\x4c\xa5\x9e\x5a\
-\xce\x36\xf2\x2c\xeb\xa8\xec\x11\x60\x8d\x6c\x37\x8c\xcb\x36\x1c\
-\xf5\x5d\xfc\x28\x14\xd3\x79\x19\x4e\xbc\x1f\x30\xa5\x19\x16\xd9\
-\x12\xa7\x77\x5e\x84\x23\x1c\xe0\x24\xc0\xf4\xc7\x2e\x6a\x2c\xa4\
-\x10\x9a\xbb\x93\x4b\x5f\xee\x37\x30\x50\x34\x76\x57\x89\xd2\x87\
-\xef\x59\x27\x4c\x8e\x26\x9d\x82\xd7\xac\x05\x84\x96\x30\xa8\xb5\
-\x34\xde\xf0\x19\x00\x45\xfa\xa2\x5a\x6b\xf4\x93\x0c\xea\x4a\xc9\
-\xaf\xba\x82\x40\xa3\xb7\x4a\x5c\x9a\xa0\xec\x2b\x96\x5d\x25\xec\
-\xba\xf4\xa3\x89\xae\xf4\xf9\xd6\x18\xa0\xcd\x2f\xba\xd6\xa6\x33\
-\x2c\xab\xba\x76\xc4\x53\x7d\xc2\xe7\x4a\xee\x7e\xd6\xfb\xd2\xc7\
-\x1e\xf6\xa2\x04\x5e\x9d\x7a\x2d\xab\xd5\xda\xc2\x61\xbd\x38\x1e\
-\x9d\xf8\x0b\x10\xe7\x8d\xb4\x4c\x30\xcb\x9c\xa9\xef\x78\xd4\x90\
-\xa8\xf3\x00\x1f\x20\x52\xf1\xc7\xc3\xc7\x5e\x02\x99\xf3\xe2\x4f\
-\x6d\x9e\x06\x11\x1e\xe7\xc0\x1e\xf5\x16\xb2\xf8\x8b\xa3\x7a\x2e\
-\x78\x01\xa3\x6f\x98\x5d\x54\x0d\x5b\x49\xc7\x9a\x9f\x60\xd4\xf3\
-\xf0\x64\x38\x73\xb6\x87\x8d\x3c\x1d\xda\x1f\x6b\x64\xcf\xa1\xab\
-\x17\x49\xf0\xa6\x72\x41\xe1\x27\x0b\x50\xfc\xe6\x92\xf8\x5e\x78\
-\x71\x68\x65\x6b\xb9\xe8\x72\xf6\x5f\xc7\x2e\xc2\xa1\x8d\xa1\xc9\
-\x70\xd2\xe5\x3c\x1c\x41\xe8\x24\x21\x97\xed\x09\x1f\x27\x11\x3c\
-\xe4\x99\x1f\x49\xe4\x18\x24\xdc\x08\x06\xcf\x91\x92\xc3\xd4\x82\
-\x9a\x1d\xa4\x99\xf0\x69\xe2\xb2\xab\x94\xd0\xbb\xa0\x61\xfb\xaa\
-\xa9\xbe\x49\xc8\x81\x5f\x71\x39\x00\xfd\xf2\xf4\x15\x23\x4c\x9e\
-\xc4\xf9\xec\xd2\xca\x5d\x50\x84\x32\x1c\xc7\xf1\x8b\xd4\x7c\xdf\
-\x19\x93\x59\x7c\x25\xf2\x9b\xd6\x0d\x30\x85\xa2\x8c\xe6\xc9\x31\
-\x6d\xbb\xd6\xbb\x95\x71\x48\x2c\x59\xdd\x4a\xff\x84\xcc\x11\x82\
-\xfc\x95\x0a\x47\x2a\x12\x12\xbd\xe6\x32\xd0\x93\xd0\x59\x72\xe9\
-\xb1\x81\xf1\xb6\x6b\x06\x5e\xe3\xcd\xb8\x30\x6a\x58\x0d\xc0\x87\
-\x13\x7c\xab\x6d\x25\xa4\xd5\xb5\x34\x4c\x39\x0a\x91\xa3\x01\xea\
-\xbf\xa6\xd7\x8f\x5f\x25\xb7\xd7\x2c\x8f\xda\x08\x69\x8e\x19\xc8\
-\x99\x9a\xbb\x2b\x59\xfa\xb7\x59\xf7\x0c\x26\x57\xd1\x68\x58\x76\
-\xcf\xc8\x99\x0d\x6c\x8a\x01\x4b\x7b\xa8\x21\x8b\xbb\xc8\x85\xbb\
-\xc7\xf7\xfd\x5d\x2f\x6e\x71\xf7\xdc\x4f\xd7\x41\x11\xdd\x9f\xef\
-\x8d\xa2\x5b\x1b\xf7\x17\x7b\xa6\x80\x2b\x21\xcd\xa8\xed\x84\x1a\
-\x68\x65\x8b\x64\xd4\x09\x06\x28\x1a\xc3\x0e\xd3\x94\xc1\xd0\x5a\
-\x31\x76\x06\xf3\xfc\xdd\x4b\x50\x84\x73\x9a\xe5\xf4\x2e\x42\x69\
-\x4a\x71\x1a\x13\xef\x57\x2f\x22\x28\x4d\x28\x8d\xc8\x64\xf6\xae\
-\xa7\x3c\x7d\x49\x3e\xad\xa0\x56\xab\x61\x2f\x6e\xcd\x33\xb3\x5b\
-\x23\xdd\x78\xfe\xcf\x40\x10\x44\x31\xc5\x79\xf6\x26\x10\xf4\xfd\
-\x03\x41\x30\x8a\x28\xc5\xd9\xec\x2d\x20\x72\xf2\x5e\x81\x98\xa1\
-\x2c\x89\x93\x7c\x96\xdd\x65\x28\x89\x00\x87\x37\x61\x78\xb1\xb3\
-\xdf\x1f\x0c\x24\x41\x24\xa3\x78\x16\xbf\x09\xc4\xbf\xde\x10\x7f\
-\x15\x70\x79\x03\xd3\x53\x95\x8d\x07\x3f\x71\x18\xb8\x98\x02\x6b\
-\x73\x84\x63\xa8\x92\x7a\x6b\x8f\xa2\x24\x23\xb3\x6c\xfc\x2d\xf9\
-\x47\x50\xc3\x16\x48\xd3\x28\xff\x8f\x00\x77\x68\xcc\xdd\xff\xa7\
-\xc5\xcd\x77\xd2\xf3\xe7\xb2\
-\x00\x00\x03\x9b\
-\x3c\
-\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
-\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x76\x69\x65\x77\x42\
-\x6f\x78\x3d\x22\x30\x20\x30\x20\x38\x30\x20\x38\x30\x22\x20\x66\
-\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x78\x6d\x6c\x6e\x73\
-\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
-\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x3e\x0a\x20\
-\x20\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\
-\x3d\x22\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x63\x6c\x69\x70\x2d\
-\x72\x75\x6c\x65\x3d\x22\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x64\
-\x3d\x22\x4d\x33\x33\x2e\x39\x32\x35\x36\x20\x39\x2e\x38\x38\x36\
-\x34\x37\x43\x33\x33\x2e\x31\x36\x34\x38\x20\x39\x2e\x38\x38\x36\
-\x34\x37\x20\x33\x32\x2e\x34\x34\x35\x34\x20\x31\x30\x2e\x32\x33\
-\x32\x39\x20\x33\x31\x2e\x39\x37\x31\x31\x20\x31\x30\x2e\x38\x32\
-\x37\x37\x4c\x32\x36\x2e\x36\x35\x30\x31\x20\x31\x37\x2e\x35\x48\
-\x31\x35\x43\x31\x33\x2e\x36\x31\x39\x33\x20\x31\x37\x2e\x35\x20\
-\x31\x32\x2e\x35\x20\x31\x38\x2e\x36\x31\x39\x33\x20\x31\x32\x2e\
-\x35\x20\x32\x30\x43\x31\x32\x2e\x35\x20\x32\x31\x2e\x33\x38\x30\
-\x37\x20\x31\x33\x2e\x36\x31\x39\x33\x20\x32\x32\x2e\x35\x20\x31\
-\x35\x20\x32\x32\x2e\x35\x48\x31\x36\x2e\x35\x56\x36\x34\x43\x31\
-\x36\x2e\x35\x20\x36\x37\x2e\x35\x38\x39\x39\x20\x31\x39\x2e\x34\
-\x31\x30\x31\x20\x37\x30\x2e\x35\x20\x32\x33\x20\x37\x30\x2e\x35\
-\x48\x35\x37\x43\x36\x30\x2e\x35\x38\x39\x38\x20\x37\x30\x2e\x35\
-\x20\x36\x33\x2e\x35\x20\x36\x37\x2e\x35\x38\x39\x39\x20\x36\x33\
-\x2e\x35\x20\x36\x34\x56\x32\x32\x2e\x35\x48\x36\x35\x43\x36\x36\
-\x2e\x33\x38\x30\x37\x20\x32\x32\x2e\x35\x20\x36\x37\x2e\x35\x20\
-\x32\x31\x2e\x33\x38\x30\x37\x20\x36\x37\x2e\x35\x20\x32\x30\x43\
-\x36\x37\x2e\x35\x20\x31\x38\x2e\x36\x31\x39\x33\x20\x36\x36\x2e\
-\x33\x38\x30\x37\x20\x31\x37\x2e\x35\x20\x36\x35\x20\x31\x37\x2e\
-\x35\x48\x35\x33\x2e\x33\x34\x39\x39\x4c\x34\x38\x2e\x30\x32\x39\
-\x20\x31\x30\x2e\x38\x32\x37\x38\x43\x34\x37\x2e\x35\x35\x34\x36\
-\x20\x31\x30\x2e\x32\x33\x32\x39\x20\x34\x36\x2e\x38\x33\x35\x32\
-\x20\x39\x2e\x38\x38\x36\x34\x37\x20\x34\x36\x2e\x30\x37\x34\x34\
-\x20\x39\x2e\x38\x38\x36\x34\x37\x48\x33\x33\x2e\x39\x32\x35\x36\
-\x5a\x4d\x33\x33\x20\x32\x37\x2e\x35\x43\x33\x34\x2e\x33\x38\x30\
-\x37\x20\x32\x37\x2e\x35\x20\x33\x35\x2e\x35\x20\x32\x38\x2e\x36\
-\x31\x39\x33\x20\x33\x35\x2e\x35\x20\x33\x30\x56\x35\x38\x43\x33\
-\x35\x2e\x35\x20\x35\x39\x2e\x33\x38\x30\x37\x20\x33\x34\x2e\x33\
-\x38\x30\x37\x20\x36\x30\x2e\x35\x20\x33\x33\x20\x36\x30\x2e\x35\
-\x43\x33\x31\x2e\x36\x31\x39\x33\x20\x36\x30\x2e\x35\x20\x33\x30\
-\x2e\x35\x20\x35\x39\x2e\x33\x38\x30\x37\x20\x33\x30\x2e\x35\x20\
-\x35\x38\x56\x33\x30\x43\x33\x30\x2e\x35\x20\x32\x38\x2e\x36\x31\
-\x39\x33\x20\x33\x31\x2e\x36\x31\x39\x33\x20\x32\x37\x2e\x35\x20\
-\x33\x33\x20\x32\x37\x2e\x35\x5a\x4d\x34\x39\x2e\x35\x20\x33\x30\
-\x43\x34\x39\x2e\x35\x20\x32\x38\x2e\x36\x31\x39\x33\x20\x34\x38\
-\x2e\x33\x38\x30\x37\x20\x32\x37\x2e\x35\x20\x34\x37\x20\x32\x37\
-\x2e\x35\x43\x34\x35\x2e\x36\x31\x39\x33\x20\x32\x37\x2e\x35\x20\
-\x34\x34\x2e\x35\x20\x32\x38\x2e\x36\x31\x39\x33\x20\x34\x34\x2e\
-\x35\x20\x33\x30\x56\x35\x38\x43\x34\x34\x2e\x35\x20\x35\x39\x2e\
-\x33\x38\x30\x37\x20\x34\x35\x2e\x36\x31\x39\x33\x20\x36\x30\x2e\
-\x35\x20\x34\x37\x20\x36\x30\x2e\x35\x43\x34\x38\x2e\x33\x38\x30\
-\x37\x20\x36\x30\x2e\x35\x20\x34\x39\x2e\x35\x20\x35\x39\x2e\x33\
-\x38\x30\x37\x20\x34\x39\x2e\x35\x20\x35\x38\x56\x33\x30\x5a\x4d\
-\x34\x36\x2e\x39\x35\x33\x36\x20\x31\x37\x2e\x34\x39\x38\x36\x4c\
-\x34\x34\x2e\x38\x37\x30\x34\x20\x31\x34\x2e\x38\x38\x36\x35\x48\
-\x33\x35\x2e\x31\x32\x39\x36\x4c\x33\x33\x2e\x30\x34\x36\x34\x20\
-\x31\x37\x2e\x34\x39\x38\x36\x48\x34\x36\x2e\x39\x35\x33\x36\x5a\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x43\x32\x43\x43\x44\x45\x22\
-\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x01\x88\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x73\x61\x76\x65\x22\
-\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x39\x20\x32\x31\
-\x48\x35\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x2d\x32\x2d\x32\
-\x56\x35\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x20\x32\x2d\x32\
-\x68\x31\x31\x6c\x35\x20\x35\x76\x31\x31\x61\x32\x20\x32\x20\x30\
-\x20\x30\x20\x31\x2d\x32\x20\x32\x7a\x22\x3e\x3c\x2f\x70\x61\x74\
-\x68\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\
-\x74\x73\x3d\x22\x31\x37\x20\x32\x31\x20\x31\x37\x20\x31\x33\x20\
-\x37\x20\x31\x33\x20\x37\x20\x32\x31\x22\x3e\x3c\x2f\x70\x6f\x6c\
-\x79\x6c\x69\x6e\x65\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\
-\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x20\x33\x20\x37\x20\x38\x20\
-\x31\x35\x20\x38\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\
-\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x01\x70\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x6c\x6f\x67\x2d\x69\
-\x6e\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x35\x20\
-\x33\x68\x34\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x20\x32\x20\
-\x32\x76\x31\x34\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x2d\x32\
-\x20\x32\x68\x2d\x34\x22\x3e\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x70\
-\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\
-\x31\x30\x20\x31\x37\x20\x31\x35\x20\x31\x32\x20\x31\x30\x20\x37\
-\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\x3c\x6c\x69\
-\x6e\x65\x20\x78\x31\x3d\x22\x31\x35\x22\x20\x79\x31\x3d\x22\x31\
-\x32\x22\x20\x78\x32\x3d\x22\x33\x22\x20\x79\x32\x3d\x22\x31\x32\
-\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x05\xba\
-\x3c\
-\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
-\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x76\x69\x65\x77\x42\
-\x6f\x78\x3d\x22\x30\x20\x30\x20\x38\x30\x20\x38\x30\x22\x20\x66\
-\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x78\x6d\x6c\x6e\x73\
-\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
-\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x3e\x0a\x20\
-\x20\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x38\x2e\x31\x34\
-\x34\x31\x20\x31\x32\x2e\x36\x32\x31\x37\x43\x33\x38\x2e\x38\x31\
-\x35\x39\x20\x31\x30\x2e\x39\x34\x38\x38\x20\x34\x31\x2e\x31\x38\
-\x34\x31\x20\x31\x30\x2e\x39\x34\x38\x38\x20\x34\x31\x2e\x38\x35\
-\x35\x39\x20\x31\x32\x2e\x36\x32\x31\x37\x4c\x34\x37\x2e\x38\x30\
-\x38\x31\x20\x32\x37\x2e\x34\x34\x33\x39\x43\x34\x38\x2e\x30\x39\
-\x34\x32\x20\x32\x38\x2e\x31\x35\x36\x33\x20\x34\x38\x2e\x37\x36\
-\x32\x38\x20\x32\x38\x2e\x36\x34\x32\x20\x34\x39\x2e\x35\x32\x38\
-\x38\x20\x32\x38\x2e\x36\x39\x34\x4c\x36\x35\x2e\x34\x36\x34\x38\
-\x20\x32\x39\x2e\x37\x37\x34\x35\x43\x36\x37\x2e\x32\x36\x33\x35\
-\x20\x32\x39\x2e\x38\x39\x36\x35\x20\x36\x37\x2e\x39\x39\x35\x33\
-\x20\x33\x32\x2e\x31\x34\x38\x39\x20\x36\x36\x2e\x36\x31\x31\x38\
-\x20\x33\x33\x2e\x33\x30\x34\x37\x4c\x35\x34\x2e\x33\x35\x34\x34\
-\x20\x34\x33\x2e\x35\x34\x35\x39\x43\x35\x33\x2e\x37\x36\x35\x33\
-\x20\x34\x34\x2e\x30\x33\x38\x31\x20\x35\x33\x2e\x35\x30\x39\x39\
-\x20\x34\x34\x2e\x38\x32\x34\x31\x20\x35\x33\x2e\x36\x39\x37\x32\
-\x20\x34\x35\x2e\x35\x36\x38\x36\x4c\x35\x37\x2e\x35\x39\x34\x31\
-\x20\x36\x31\x2e\x30\x35\x38\x36\x43\x35\x38\x2e\x30\x33\x33\x39\
-\x20\x36\x32\x2e\x38\x30\x36\x39\x20\x35\x36\x2e\x31\x31\x37\x39\
-\x20\x36\x34\x2e\x31\x39\x39\x20\x35\x34\x2e\x35\x39\x31\x31\x20\
-\x36\x33\x2e\x32\x34\x30\x34\x4c\x34\x31\x2e\x30\x36\x33\x34\x20\
-\x35\x34\x2e\x37\x34\x37\x36\x43\x34\x30\x2e\x34\x31\x33\x32\x20\
-\x35\x34\x2e\x33\x33\x39\x34\x20\x33\x39\x2e\x35\x38\x36\x38\x20\
-\x35\x34\x2e\x33\x33\x39\x34\x20\x33\x38\x2e\x39\x33\x36\x36\x20\
-\x35\x34\x2e\x37\x34\x37\x36\x4c\x32\x35\x2e\x34\x30\x38\x39\x20\
-\x36\x33\x2e\x32\x34\x30\x34\x43\x32\x33\x2e\x38\x38\x32\x31\x20\
-\x36\x34\x2e\x31\x39\x39\x20\x32\x31\x2e\x39\x36\x36\x31\x20\x36\
-\x32\x2e\x38\x30\x36\x39\x20\x32\x32\x2e\x34\x30\x35\x39\x20\x36\
-\x31\x2e\x30\x35\x38\x36\x4c\x32\x36\x2e\x33\x30\x32\x38\x20\x34\
-\x35\x2e\x35\x36\x38\x36\x43\x32\x36\x2e\x34\x39\x30\x31\x20\x34\
-\x34\x2e\x38\x32\x34\x31\x20\x32\x36\x2e\x32\x33\x34\x37\x20\x34\
-\x34\x2e\x30\x33\x38\x31\x20\x32\x35\x2e\x36\x34\x35\x36\x20\x34\
-\x33\x2e\x35\x34\x35\x39\x4c\x31\x33\x2e\x33\x38\x38\x32\x20\x33\
-\x33\x2e\x33\x30\x34\x37\x43\x31\x32\x2e\x30\x30\x34\x37\x20\x33\
-\x32\x2e\x31\x34\x38\x39\x20\x31\x32\x2e\x37\x33\x36\x35\x20\x32\
-\x39\x2e\x38\x39\x36\x35\x20\x31\x34\x2e\x35\x33\x35\x32\x20\x32\
-\x39\x2e\x37\x37\x34\x35\x4c\x33\x30\x2e\x34\x37\x31\x32\x20\x32\
-\x38\x2e\x36\x39\x34\x43\x33\x31\x2e\x32\x33\x37\x32\x20\x32\x38\
-\x2e\x36\x34\x32\x20\x33\x31\x2e\x39\x30\x35\x38\x20\x32\x38\x2e\
-\x31\x35\x36\x33\x20\x33\x32\x2e\x31\x39\x31\x39\x20\x32\x37\x2e\
-\x34\x34\x33\x39\x4c\x33\x38\x2e\x31\x34\x34\x31\x20\x31\x32\x2e\
-\x36\x32\x31\x37\x5a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\
-\x39\x39\x34\x41\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
-\x20\x64\x3d\x22\x4d\x33\x39\x2e\x30\x35\x31\x35\x20\x32\x36\x2e\
-\x33\x31\x30\x38\x43\x33\x39\x2e\x33\x38\x37\x34\x20\x32\x35\x2e\
-\x34\x37\x34\x34\x20\x34\x30\x2e\x35\x37\x31\x36\x20\x32\x35\x2e\
-\x34\x37\x34\x34\x20\x34\x30\x2e\x39\x30\x37\x35\x20\x32\x36\x2e\
-\x33\x31\x30\x38\x4c\x34\x33\x2e\x38\x38\x33\x35\x20\x33\x33\x2e\
-\x37\x32\x31\x39\x43\x34\x34\x2e\x30\x32\x36\x36\x20\x33\x34\x2e\
-\x30\x37\x38\x31\x20\x34\x34\x2e\x33\x36\x30\x39\x20\x33\x34\x2e\
-\x33\x32\x31\x20\x34\x34\x2e\x37\x34\x33\x39\x20\x33\x34\x2e\x33\
-\x34\x37\x4c\x35\x32\x2e\x37\x31\x31\x39\x20\x33\x34\x2e\x38\x38\
-\x37\x33\x43\x35\x33\x2e\x36\x31\x31\x32\x20\x33\x34\x2e\x39\x34\
-\x38\x32\x20\x35\x33\x2e\x39\x37\x37\x31\x20\x33\x36\x2e\x30\x37\
-\x34\x34\x20\x35\x33\x2e\x32\x38\x35\x34\x20\x33\x36\x2e\x36\x35\
-\x32\x34\x4c\x34\x37\x2e\x31\x35\x36\x37\x20\x34\x31\x2e\x37\x37\
-\x32\x39\x43\x34\x36\x2e\x38\x36\x32\x31\x20\x34\x32\x2e\x30\x31\
-\x39\x31\x20\x34\x36\x2e\x37\x33\x34\x34\x20\x34\x32\x2e\x34\x31\
-\x32\x31\x20\x34\x36\x2e\x38\x32\x38\x31\x20\x34\x32\x2e\x37\x38\
-\x34\x33\x4c\x34\x38\x2e\x37\x37\x36\x35\x20\x35\x30\x2e\x35\x32\
-\x39\x33\x43\x34\x38\x2e\x39\x39\x36\x34\x20\x35\x31\x2e\x34\x30\
-\x33\x35\x20\x34\x38\x2e\x30\x33\x38\x34\x20\x35\x32\x2e\x30\x39\
-\x39\x35\x20\x34\x37\x2e\x32\x37\x35\x20\x35\x31\x2e\x36\x32\x30\
-\x32\x4c\x34\x30\x2e\x35\x31\x31\x32\x20\x34\x37\x2e\x33\x37\x33\
-\x38\x43\x34\x30\x2e\x31\x38\x36\x31\x20\x34\x37\x2e\x31\x36\x39\
-\x37\x20\x33\x39\x2e\x37\x37\x32\x39\x20\x34\x37\x2e\x31\x36\x39\
-\x37\x20\x33\x39\x2e\x34\x34\x37\x38\x20\x34\x37\x2e\x33\x37\x33\
-\x38\x4c\x33\x32\x2e\x36\x38\x34\x20\x35\x31\x2e\x36\x32\x30\x32\
-\x43\x33\x31\x2e\x39\x32\x30\x35\x20\x35\x32\x2e\x30\x39\x39\x35\
-\x20\x33\x30\x2e\x39\x36\x32\x36\x20\x35\x31\x2e\x34\x30\x33\x35\
-\x20\x33\x31\x2e\x31\x38\x32\x35\x20\x35\x30\x2e\x35\x32\x39\x33\
-\x4c\x33\x33\x2e\x31\x33\x30\x39\x20\x34\x32\x2e\x37\x38\x34\x33\
-\x43\x33\x33\x2e\x32\x32\x34\x35\x20\x34\x32\x2e\x34\x31\x32\x31\
-\x20\x33\x33\x2e\x30\x39\x36\x38\x20\x34\x32\x2e\x30\x31\x39\x31\
-\x20\x33\x32\x2e\x38\x30\x32\x33\x20\x34\x31\x2e\x37\x37\x32\x39\
-\x4c\x32\x36\x2e\x36\x37\x33\x36\x20\x33\x36\x2e\x36\x35\x32\x34\
-\x43\x32\x35\x2e\x39\x38\x31\x38\x20\x33\x36\x2e\x30\x37\x34\x34\
-\x20\x32\x36\x2e\x33\x34\x37\x38\x20\x33\x34\x2e\x39\x34\x38\x32\
-\x20\x32\x37\x2e\x32\x34\x37\x31\x20\x33\x34\x2e\x38\x38\x37\x33\
-\x4c\x33\x35\x2e\x32\x31\x35\x31\x20\x33\x34\x2e\x33\x34\x37\x43\
-\x33\x35\x2e\x35\x39\x38\x31\x20\x33\x34\x2e\x33\x32\x31\x20\x33\
-\x35\x2e\x39\x33\x32\x34\x20\x33\x34\x2e\x30\x37\x38\x31\x20\x33\
-\x36\x2e\x30\x37\x35\x34\x20\x33\x33\x2e\x37\x32\x31\x39\x4c\x33\
-\x39\x2e\x30\x35\x31\x35\x20\x32\x36\x2e\x33\x31\x30\x38\x5a\x22\
-\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\x43\x39\x34\x43\x22\x20\
-\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x04\xc2\
-\x3c\
-\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
-\x2d\x38\x22\x3f\x3e\x3c\x21\x2d\x2d\x20\x55\x70\x6c\x6f\x61\x64\
-\x65\x64\x20\x74\x6f\x3a\x20\x53\x56\x47\x20\x52\x65\x70\x6f\x2c\
-\x20\x77\x77\x77\x2e\x73\x76\x67\x72\x65\x70\x6f\x2e\x63\x6f\x6d\
-\x2c\x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x53\x56\x47\
-\x20\x52\x65\x70\x6f\x20\x4d\x69\x78\x65\x72\x20\x54\x6f\x6f\x6c\
-\x73\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
-\x3d\x22\x38\x30\x30\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\
-\x22\x38\x30\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\
-\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x66\x69\x6c\x6c\
-\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\
-\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\
-\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x3e\x0a\x3c\x70\x61\x74\
-\x68\x20\x64\x3d\x22\x4d\x32\x30\x20\x39\x2e\x35\x30\x31\x39\x35\
-\x56\x38\x2e\x37\x34\x39\x38\x35\x43\x32\x30\x20\x37\x2e\x35\x30\
-\x37\x32\x31\x20\x31\x38\x2e\x39\x39\x32\x36\x20\x36\x2e\x34\x39\
-\x39\x38\x35\x20\x31\x37\x2e\x37\x35\x20\x36\x2e\x34\x39\x39\x38\
-\x35\x48\x31\x32\x2e\x30\x32\x34\x37\x4c\x39\x2e\x36\x34\x33\x36\
-\x38\x20\x34\x2e\x35\x31\x39\x39\x35\x43\x39\x2e\x32\x33\x39\x35\
-\x39\x20\x34\x2e\x31\x38\x33\x39\x33\x20\x38\x2e\x37\x33\x30\x36\
-\x33\x20\x33\x2e\x39\x39\x39\x39\x37\x20\x38\x2e\x32\x30\x35\x30\
-\x39\x20\x33\x2e\x39\x39\x39\x39\x37\x48\x34\x2e\x32\x34\x39\x35\
-\x37\x43\x33\x2e\x30\x30\x37\x32\x34\x20\x33\x2e\x39\x39\x39\x39\
-\x37\x20\x32\x20\x35\x2e\x30\x30\x36\x38\x36\x20\x31\x2e\x39\x39\
-\x39\x35\x37\x20\x36\x2e\x32\x34\x39\x31\x39\x4c\x31\x2e\x39\x39\
-\x35\x36\x31\x20\x31\x37\x2e\x37\x34\x39\x32\x43\x31\x2e\x39\x39\
-\x35\x31\x38\x20\x31\x38\x2e\x39\x39\x32\x31\x20\x33\x2e\x30\x30\
-\x32\x36\x36\x20\x32\x30\x20\x34\x2e\x32\x34\x35\x36\x31\x20\x32\
-\x30\x48\x34\x2e\x32\x37\x31\x39\x36\x43\x34\x2e\x32\x37\x36\x30\
-\x37\x20\x32\x30\x20\x34\x2e\x32\x38\x30\x31\x39\x20\x32\x30\x20\
-\x34\x2e\x32\x38\x34\x33\x31\x20\x32\x30\x48\x31\x38\x2e\x34\x36\
-\x39\x33\x43\x31\x39\x2e\x32\x37\x32\x33\x20\x32\x30\x20\x31\x39\
-\x2e\x39\x37\x32\x33\x20\x31\x39\x2e\x34\x35\x33\x35\x20\x32\x30\
-\x2e\x31\x36\x37\x20\x31\x38\x2e\x36\x37\x34\x35\x4c\x32\x31\x2e\
-\x39\x31\x36\x39\x20\x31\x31\x2e\x36\x37\x36\x35\x43\x32\x32\x2e\
-\x31\x39\x33\x31\x20\x31\x30\x2e\x35\x37\x31\x39\x20\x32\x31\x2e\
-\x33\x35\x37\x37\x20\x39\x2e\x35\x30\x31\x39\x35\x20\x32\x30\x2e\
-\x32\x31\x39\x32\x20\x39\x2e\x35\x30\x31\x39\x35\x48\x32\x30\x5a\
-\x4d\x34\x2e\x32\x34\x39\x35\x37\x20\x35\x2e\x34\x39\x39\x39\x37\
-\x48\x38\x2e\x32\x30\x35\x30\x39\x43\x38\x2e\x33\x38\x30\x32\x37\
-\x20\x35\x2e\x34\x39\x39\x39\x37\x20\x38\x2e\x35\x34\x39\x39\x33\
-\x20\x35\x2e\x35\x36\x31\x32\x39\x20\x38\x2e\x36\x38\x34\x36\x32\
-\x20\x35\x2e\x36\x37\x33\x33\x4c\x31\x31\x2e\x32\x37\x34\x31\x20\
-\x37\x2e\x38\x32\x36\x35\x32\x43\x31\x31\x2e\x34\x30\x38\x38\x20\
-\x37\x2e\x39\x33\x38\x35\x32\x20\x31\x31\x2e\x35\x37\x38\x34\x20\
-\x37\x2e\x39\x39\x39\x38\x35\x20\x31\x31\x2e\x37\x35\x33\x36\x20\
-\x37\x2e\x39\x39\x39\x38\x35\x48\x31\x37\x2e\x37\x35\x43\x31\x38\
-\x2e\x31\x36\x34\x32\x20\x37\x2e\x39\x39\x39\x38\x35\x20\x31\x38\
-\x2e\x35\x20\x38\x2e\x33\x33\x35\x36\x33\x20\x31\x38\x2e\x35\x20\
-\x38\x2e\x37\x34\x39\x38\x35\x56\x39\x2e\x35\x30\x31\x39\x35\x48\
-\x36\x2e\x34\x32\x33\x38\x35\x43\x35\x2e\x33\x39\x31\x33\x36\x20\
-\x39\x2e\x35\x30\x31\x39\x35\x20\x34\x2e\x34\x39\x31\x33\x37\x20\
-\x31\x30\x2e\x32\x30\x34\x37\x20\x34\x2e\x32\x34\x31\x20\x31\x31\
-\x2e\x32\x30\x36\x34\x4c\x33\x2e\x34\x39\x36\x38\x34\x20\x31\x34\
-\x2e\x31\x38\x33\x37\x4c\x33\x2e\x34\x39\x39\x35\x37\x20\x36\x2e\
-\x32\x34\x39\x37\x31\x43\x33\x2e\x34\x39\x39\x37\x31\x20\x35\x2e\
-\x38\x33\x35\x36\x20\x33\x2e\x38\x33\x35\x34\x36\x20\x35\x2e\x34\
-\x39\x39\x39\x37\x20\x34\x2e\x32\x34\x39\x35\x37\x20\x35\x2e\x34\
-\x39\x39\x39\x37\x5a\x4d\x35\x2e\x36\x39\x36\x32\x33\x20\x31\x31\
-\x2e\x35\x37\x30\x31\x43\x35\x2e\x37\x37\x39\x36\x39\x20\x31\x31\
-\x2e\x32\x33\x36\x32\x20\x36\x2e\x30\x37\x39\x36\x39\x20\x31\x31\
-\x2e\x30\x30\x32\x20\x36\x2e\x34\x32\x33\x38\x35\x20\x31\x31\x2e\
-\x30\x30\x32\x48\x32\x30\x2e\x32\x31\x39\x32\x43\x32\x30\x2e\x33\
-\x38\x31\x39\x20\x31\x31\x2e\x30\x30\x32\x20\x32\x30\x2e\x35\x30\
-\x31\x32\x20\x31\x31\x2e\x31\x35\x34\x38\x20\x32\x30\x2e\x34\x36\
-\x31\x37\x20\x31\x31\x2e\x33\x31\x32\x36\x4c\x31\x38\x2e\x37\x31\
-\x31\x39\x20\x31\x38\x2e\x33\x31\x30\x37\x43\x31\x38\x2e\x36\x38\
-\x34\x20\x31\x38\x2e\x34\x32\x31\x39\x20\x31\x38\x2e\x35\x38\x34\
-\x20\x31\x38\x2e\x35\x20\x31\x38\x2e\x34\x36\x39\x33\x20\x31\x38\
-\x2e\x35\x48\x34\x2e\x32\x38\x34\x33\x31\x43\x34\x2e\x31\x32\x31\
-\x36\x37\x20\x31\x38\x2e\x35\x20\x34\x2e\x30\x30\x32\x33\x33\x20\
-\x31\x38\x2e\x33\x34\x37\x32\x20\x34\x2e\x30\x34\x31\x37\x37\x20\
-\x31\x38\x2e\x31\x38\x39\x34\x4c\x35\x2e\x36\x39\x36\x32\x33\x20\
-\x31\x31\x2e\x35\x37\x30\x31\x5a\x22\x20\x66\x69\x6c\x6c\x3d\x22\
-\x23\x32\x31\x32\x31\x32\x31\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\
-\x3e\
-\x00\x00\x09\x8f\
-\x3c\
-\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
-\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\
-\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\
-\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\
-\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\
-\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\
-\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\
-\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\
-\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\
-\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
-\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
-\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
-\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\
-\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
-\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\
-\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
-\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
-\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\
-\x6f\x64\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\
-\x70\x6f\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\
-\x2e\x6e\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\
-\x69\x2d\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\
-\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\x22\x68\x74\x74\x70\
-\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\
-\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\x63\x65\x73\x2f\x69\
-\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\
-\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x72\x63\x31\x20\x28\x30\x39\x39\x36\x30\x64\x36\x66\x30\x35\
-\x2c\x20\x32\x30\x32\x30\x2d\x30\x34\x2d\x30\x39\x29\x22\x0a\x20\
-\x20\x20\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\
-\x6d\x65\x3d\x22\x63\x6f\x6c\x6c\x61\x70\x73\x65\x2e\x73\x76\x67\
-\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x76\x67\x36\x22\x0a\x20\
-\x20\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\
-\x20\x20\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\x65\
-\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x63\x6f\x64\x65\x22\x0a\
-\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\
-\x69\x6e\x3d\x22\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\
-\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3d\x22\x72\x6f\
-\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\
-\x69\x64\x74\x68\x3d\x22\x32\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\
-\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\x6f\x72\
-\x22\x0a\x20\x20\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\
-\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\
-\x20\x32\x34\x20\x32\x34\x22\x0a\x20\x20\x20\x68\x65\x69\x67\x68\
-\x74\x3d\x22\x32\x34\x22\x0a\x20\x20\x20\x77\x69\x64\x74\x68\x3d\
-\x22\x32\x34\x22\x3e\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\
-\x61\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\x61\x64\
-\x61\x74\x61\x31\x32\x22\x3e\x0a\x20\x20\x20\x20\x3c\x72\x64\x66\
-\x3a\x52\x44\x46\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x63\x63\x3a\
-\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x72\x64\
-\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x0a\x20\x20\x20\x20\
-\x20\x20\x20\x20\x3c\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x69\
-\x6d\x61\x67\x65\x2f\x73\x76\x67\x2b\x78\x6d\x6c\x3c\x2f\x64\x63\
-\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\
-\x20\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\x20\x20\
-\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\x72\x63\
-\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\x2e\x6f\
-\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\x2f\x53\
-\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x0a\x20\x20\
-\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\
-\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x20\x20\
-\x20\x20\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x0a\x20\x20\x20\
-\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\x3c\x2f\
-\x6d\x65\x74\x61\x64\x61\x74\x61\x3e\x0a\x20\x20\x3c\x64\x65\x66\
-\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\x73\x31\
-\x30\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x73\x6f\x64\x69\x70\x6f\x64\
-\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\x20\x20\
-\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\x65\x6e\
-\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\x67\x36\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
-\x64\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\x22\x30\
-\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\
-\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x31\x38\x35\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
-\x64\x6f\x77\x2d\x78\x3d\x22\x31\x33\x38\x37\x22\x0a\x20\x20\x20\
-\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\x22\x31\
-\x32\x2e\x31\x38\x37\x33\x38\x32\x22\x0a\x20\x20\x20\x20\x20\x69\
-\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\x3d\x22\x31\x32\x2e\x34\
-\x37\x33\x33\x38\x37\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\
-\x63\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x32\x39\x2e\x36\x39\
-\x38\x34\x38\x35\x22\x0a\x20\x20\x20\x20\x20\x73\x68\x6f\x77\x67\
-\x72\x69\x64\x3d\x22\x66\x61\x6c\x73\x65\x22\x0a\x20\x20\x20\x20\
-\x20\x69\x64\x3d\x22\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x38\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
-\x69\x6e\x64\x6f\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x31\x31\
-\x32\x38\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\
-\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\
-\x31\x39\x37\x34\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\
-\x61\x70\x65\x3a\x70\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\
-\x32\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\
-\x3a\x70\x61\x67\x65\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x22\
-\x0a\x20\x20\x20\x20\x20\x67\x75\x69\x64\x65\x74\x6f\x6c\x65\x72\
-\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x67\
-\x72\x69\x64\x74\x6f\x6c\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\
-\x22\x0a\x20\x20\x20\x20\x20\x6f\x62\x6a\x65\x63\x74\x74\x6f\x6c\
-\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\
-\x20\x62\x6f\x72\x64\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\
-\x31\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\
-\x6c\x6f\x72\x3d\x22\x23\x36\x36\x36\x36\x36\x36\x22\x0a\x20\x20\
-\x20\x20\x20\x70\x61\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\
-\x66\x66\x66\x66\x66\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\
-\x68\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\
-\x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x23\
-\x30\x30\x30\x30\x30\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\
-\x64\x74\x68\x3a\x31\x2e\x38\x39\x34\x34\x33\x3b\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\
-\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\
-\x3a\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\
-\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\x6b\
-\x65\x2d\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\x65\
-\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\
-\x31\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\x22\x4d\x20\x32\x2e\x38\
-\x39\x35\x37\x37\x30\x36\x2c\x33\x2e\x37\x33\x37\x35\x36\x34\x34\
-\x20\x48\x20\x32\x30\x2e\x32\x33\x36\x37\x32\x33\x22\x0a\x20\x20\
-\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x38\x35\x37\x22\x0a\
-\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\
-\x6e\x6e\x65\x63\x74\x6f\x72\x2d\x63\x75\x72\x76\x61\x74\x75\x72\
-\x65\x3d\x22\x30\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
-\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\
-\x6c\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x30\
-\x30\x30\x30\x30\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\
-\x74\x68\x3a\x31\x2e\x38\x39\x34\x34\x33\x3b\x73\x74\x72\x6f\x6b\
-\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\
-\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\
-\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\
-\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\x6b\x65\
-\x2d\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\x65\x3b\
-\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\
-\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\x22\x4d\x20\x32\x2e\x38\x39\
-\x35\x37\x37\x30\x36\x2c\x31\x31\x2e\x34\x33\x37\x31\x37\x32\x20\
-\x48\x20\x32\x30\x2e\x32\x33\x36\x37\x32\x33\x22\x0a\x20\x20\x20\
-\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x38\x35\x39\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\x6e\
-\x6e\x65\x63\x74\x6f\x72\x2d\x63\x75\x72\x76\x61\x74\x75\x72\x65\
-\x3d\x22\x30\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x0a\
-\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\
-\x6e\x6e\x65\x63\x74\x6f\x72\x2d\x63\x75\x72\x76\x61\x74\x75\x72\
-\x65\x3d\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x70\
-\x61\x74\x68\x38\x35\x35\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\x22\
-\x4d\x20\x32\x2e\x38\x39\x35\x37\x37\x30\x36\x2c\x37\x2e\x35\x38\
-\x37\x33\x36\x38\x31\x20\x48\x20\x32\x30\x2e\x32\x33\x36\x37\x32\
-\x33\x22\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\
-\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\
-\x23\x30\x30\x30\x30\x30\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\
-\x69\x64\x74\x68\x3a\x31\x2e\x38\x39\x34\x34\x33\x3b\x73\x74\x72\
-\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\
-\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\
-\x6e\x3a\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\
-\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\
-\x6b\x65\x2d\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\
-\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\
-\x3a\x31\x22\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
-\x00\x00\x07\x9d\
-\x3c\
-\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
-\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\
-\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\
-\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\
-\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\
-\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\
-\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\
-\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\
-\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\
-\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
-\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
-\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
-\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\
-\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
-\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\
-\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
-\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
-\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\
-\x6f\x64\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\
-\x70\x6f\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\
-\x2e\x6e\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\
-\x69\x2d\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\
-\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\x22\x68\x74\x74\x70\
-\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\
-\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\x63\x65\x73\x2f\x69\
-\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\
-\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x72\x63\x31\x20\x28\x30\x39\x39\x36\x30\x64\x36\x66\x30\x35\
-\x2c\x20\x32\x30\x32\x30\x2d\x30\x34\x2d\x30\x39\x29\x22\x0a\x20\
-\x20\x20\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\
-\x6d\x65\x3d\x22\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\
-\x6e\x2d\x72\x65\x64\x2e\x73\x76\x67\x22\x0a\x20\x20\x20\x69\x64\
-\x3d\x22\x73\x76\x67\x38\x22\x0a\x20\x20\x20\x76\x65\x72\x73\x69\
-\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\x20\x20\x20\x63\x6c\x61\x73\
-\x73\x3d\x22\x66\x65\x61\x74\x68\x65\x72\x20\x66\x65\x61\x74\x68\
-\x65\x72\x2d\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\x6e\
-\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\
-\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3d\x22\
-\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\
-\x2d\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x0a\x20\x20\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x0a\x20\x20\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\
-\x65\x22\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
-\x20\x30\x20\x32\x34\x20\x32\x34\x22\x0a\x20\x20\x20\x68\x65\x69\
-\x67\x68\x74\x3d\x22\x32\x34\x22\x0a\x20\x20\x20\x77\x69\x64\x74\
-\x68\x3d\x22\x32\x34\x22\x3e\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\
-\x61\x74\x61\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\
-\x61\x64\x61\x74\x61\x31\x34\x22\x3e\x0a\x20\x20\x20\x20\x3c\x72\
-\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x63\
-\x63\x3a\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
-\x72\x64\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x0a\x20\x20\
-\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\
-\x3e\x69\x6d\x61\x67\x65\x2f\x73\x76\x67\x2b\x78\x6d\x6c\x3c\x2f\
-\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x0a\x20\x20\x20\x20\x20\
-\x20\x20\x20\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\
-\x20\x20\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\
-\x72\x63\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\
-\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\
-\x2f\x53\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x0a\
-\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x74\x69\x74\x6c\
-\x65\x3e\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\
-\x20\x20\x20\x20\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x0a\x20\
-\x20\x20\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\
-\x3c\x2f\x6d\x65\x74\x61\x64\x61\x74\x61\x3e\x0a\x20\x20\x3c\x64\
-\x65\x66\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\
-\x73\x31\x32\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x73\x6f\x64\x69\x70\
-\x6f\x64\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\
-\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\
-\x65\x6e\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\x67\x38\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
-\x69\x6e\x64\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\
-\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\
-\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x32\x38\x31\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
-\x69\x6e\x64\x6f\x77\x2d\x78\x3d\x22\x31\x34\x33\x33\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\
-\x22\x31\x32\x2e\x34\x30\x39\x37\x39\x37\x22\x0a\x20\x20\x20\x20\
-\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\x3d\x22\x31\x32\
-\x2e\x38\x32\x38\x32\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\
-\x6b\x73\x63\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x32\x31\x22\
-\x0a\x20\x20\x20\x20\x20\x73\x68\x6f\x77\x67\x72\x69\x64\x3d\x22\
-\x66\x61\x6c\x73\x65\x22\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\
-\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x31\x30\x22\x0a\x20\x20\x20\
-\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\
-\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x39\x35\x39\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
-\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\x31\x35\x34\x32\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\
-\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\x32\x22\x0a\x20\x20\
-\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\x65\
-\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x22\x0a\x20\x20\x20\x20\
-\x20\x67\x75\x69\x64\x65\x74\x6f\x6c\x65\x72\x61\x6e\x63\x65\x3d\
-\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x67\x72\x69\x64\x74\x6f\
-\x6c\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\
-\x20\x20\x6f\x62\x6a\x65\x63\x74\x74\x6f\x6c\x65\x72\x61\x6e\x63\
-\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\
-\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x31\x22\x0a\x20\x20\
-\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\x6c\x6f\x72\x3d\x22\
-\x23\x36\x36\x36\x36\x36\x36\x22\x0a\x20\x20\x20\x20\x20\x70\x61\
-\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\x66\x66\x66\x66\x66\
-\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x0a\
-\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\
-\x3a\x23\x30\x30\x66\x66\x30\x30\x22\x0a\x20\x20\x20\x20\x20\x69\
-\x64\x3d\x22\x70\x6f\x6c\x79\x67\x6f\x6e\x32\x22\x0a\x20\x20\x20\
-\x20\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x2e\x38\x36\x20\x32\
-\x20\x31\x36\x2e\x31\x34\x20\x32\x20\x32\x32\x20\x37\x2e\x38\x36\
-\x20\x32\x32\x20\x31\x36\x2e\x31\x34\x20\x31\x36\x2e\x31\x34\x20\
-\x32\x32\x20\x37\x2e\x38\x36\x20\x32\x32\x20\x32\x20\x31\x36\x2e\
-\x31\x34\x20\x32\x20\x37\x2e\x38\x36\x20\x37\x2e\x38\x36\x20\x32\
-\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\
-\x20\x20\x69\x64\x3d\x22\x6c\x69\x6e\x65\x34\x22\x0a\x20\x20\x20\
-\x20\x20\x79\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x78\
-\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\x22\
-\x38\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\x22\x20\
-\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\x20\x20\
-\x69\x64\x3d\x22\x6c\x69\x6e\x65\x36\x22\x0a\x20\x20\x20\x20\x20\
-\x79\x32\x3d\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x32\x3d\
-\x22\x31\x32\x2e\x30\x31\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\
-\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\
-\x22\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
-\x00\x00\x01\x90\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x72\x65\x66\x72\x65\
-\x73\x68\x2d\x63\x77\x22\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\
-\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x20\x34\x20\x32\x33\
-\x20\x31\x30\x20\x31\x37\x20\x31\x30\x22\x3e\x3c\x2f\x70\x6f\x6c\
-\x79\x6c\x69\x6e\x65\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\
-\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x20\x32\x30\x20\x31\x20\x31\
-\x34\x20\x37\x20\x31\x34\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\
-\x6e\x65\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x2e\x35\
-\x31\x20\x39\x61\x39\x20\x39\x20\x30\x20\x30\x20\x31\x20\x31\x34\
-\x2e\x38\x35\x2d\x33\x2e\x33\x36\x4c\x32\x33\x20\x31\x30\x4d\x31\
-\x20\x31\x34\x6c\x34\x2e\x36\x34\x20\x34\x2e\x33\x36\x41\x39\x20\
-\x39\x20\x30\x20\x30\x20\x30\x20\x32\x30\x2e\x34\x39\x20\x31\x35\
-\x22\x3e\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x01\x76\
-\x3c\
-\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
-\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
-\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
-\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
-\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
-\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
-\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
-\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
-\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
-\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x64\x65\x6c\x65\x74\
-\x65\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x31\x20\
-\x34\x48\x38\x6c\x2d\x37\x20\x38\x20\x37\x20\x38\x68\x31\x33\x61\
-\x32\x20\x32\x20\x30\x20\x30\x20\x30\x20\x32\x2d\x32\x56\x36\x61\
-\x32\x20\x32\x20\x30\x20\x30\x20\x30\x2d\x32\x2d\x32\x7a\x22\x3e\
-\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\
-\x22\x31\x38\x22\x20\x79\x31\x3d\x22\x39\x22\x20\x78\x32\x3d\x22\
-\x31\x32\x22\x20\x79\x32\x3d\x22\x31\x35\x22\x3e\x3c\x2f\x6c\x69\
-\x6e\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\x22\x31\x32\x22\
-\x20\x79\x31\x3d\x22\x39\x22\x20\x78\x32\x3d\x22\x31\x38\x22\x20\
-\x79\x32\x3d\x22\x31\x35\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\
-\x2f\x73\x76\x67\x3e\
-\x00\x00\x07\x9d\
-\x3c\
-\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
-\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\
-\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\
-\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\
-\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\
-\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\
-\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\
-\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\
-\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\
-\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
-\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
-\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
-\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\
-\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
-\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\
-\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
-\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
-\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\
-\x6f\x64\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\
-\x70\x6f\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\
-\x2e\x6e\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\
-\x69\x2d\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\
-\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\x22\x68\x74\x74\x70\
-\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\
-\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\x63\x65\x73\x2f\x69\
-\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\
-\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
-\x30\x72\x63\x31\x20\x28\x30\x39\x39\x36\x30\x64\x36\x66\x30\x35\
-\x2c\x20\x32\x30\x32\x30\x2d\x30\x34\x2d\x30\x39\x29\x22\x0a\x20\
-\x20\x20\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\
-\x6d\x65\x3d\x22\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\
-\x6e\x2d\x72\x65\x64\x2e\x73\x76\x67\x22\x0a\x20\x20\x20\x69\x64\
-\x3d\x22\x73\x76\x67\x38\x22\x0a\x20\x20\x20\x76\x65\x72\x73\x69\
-\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\x20\x20\x20\x63\x6c\x61\x73\
-\x73\x3d\x22\x66\x65\x61\x74\x68\x65\x72\x20\x66\x65\x61\x74\x68\
-\x65\x72\x2d\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\x6e\
-\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
-\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\
-\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3d\x22\
-\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\
-\x2d\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x0a\x20\x20\x20\x73\x74\
-\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
-\x6f\x72\x22\x0a\x20\x20\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\
-\x65\x22\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
-\x20\x30\x20\x32\x34\x20\x32\x34\x22\x0a\x20\x20\x20\x68\x65\x69\
-\x67\x68\x74\x3d\x22\x32\x34\x22\x0a\x20\x20\x20\x77\x69\x64\x74\
-\x68\x3d\x22\x32\x34\x22\x3e\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\
-\x61\x74\x61\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\
-\x61\x64\x61\x74\x61\x31\x34\x22\x3e\x0a\x20\x20\x20\x20\x3c\x72\
-\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x63\
-\x63\x3a\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
-\x72\x64\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x0a\x20\x20\
-\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\
-\x3e\x69\x6d\x61\x67\x65\x2f\x73\x76\x67\x2b\x78\x6d\x6c\x3c\x2f\
-\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x0a\x20\x20\x20\x20\x20\
-\x20\x20\x20\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\
-\x20\x20\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\
-\x72\x63\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\
-\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\
-\x2f\x53\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x0a\
-\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x74\x69\x74\x6c\
-\x65\x3e\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\
-\x20\x20\x20\x20\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x0a\x20\
-\x20\x20\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\
-\x3c\x2f\x6d\x65\x74\x61\x64\x61\x74\x61\x3e\x0a\x20\x20\x3c\x64\
-\x65\x66\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\
-\x73\x31\x32\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x73\x6f\x64\x69\x70\
-\x6f\x64\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\
-\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\
-\x65\x6e\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\x67\x38\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
-\x69\x6e\x64\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\
-\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\
-\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x32\x38\x31\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
-\x69\x6e\x64\x6f\x77\x2d\x78\x3d\x22\x31\x34\x33\x33\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\
-\x22\x31\x32\x2e\x34\x30\x39\x37\x39\x37\x22\x0a\x20\x20\x20\x20\
-\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\x3d\x22\x31\x32\
-\x2e\x38\x32\x38\x32\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\
-\x6b\x73\x63\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x32\x31\x22\
-\x0a\x20\x20\x20\x20\x20\x73\x68\x6f\x77\x67\x72\x69\x64\x3d\x22\
-\x66\x61\x6c\x73\x65\x22\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\
-\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x31\x30\x22\x0a\x20\x20\x20\
-\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\
-\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x39\x35\x39\x22\x0a\x20\
-\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
-\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\x31\x35\x34\x32\x22\
-\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\
-\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\x32\x22\x0a\x20\x20\
-\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\x65\
-\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x22\x0a\x20\x20\x20\x20\
-\x20\x67\x75\x69\x64\x65\x74\x6f\x6c\x65\x72\x61\x6e\x63\x65\x3d\
-\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x67\x72\x69\x64\x74\x6f\
-\x6c\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\
-\x20\x20\x6f\x62\x6a\x65\x63\x74\x74\x6f\x6c\x65\x72\x61\x6e\x63\
-\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\
-\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x31\x22\x0a\x20\x20\
-\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\x6c\x6f\x72\x3d\x22\
-\x23\x36\x36\x36\x36\x36\x36\x22\x0a\x20\x20\x20\x20\x20\x70\x61\
-\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\x66\x66\x66\x66\x66\
-\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x0a\
-\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\
-\x3a\x23\x66\x66\x30\x30\x30\x30\x22\x0a\x20\x20\x20\x20\x20\x69\
-\x64\x3d\x22\x70\x6f\x6c\x79\x67\x6f\x6e\x32\x22\x0a\x20\x20\x20\
-\x20\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x2e\x38\x36\x20\x32\
-\x20\x31\x36\x2e\x31\x34\x20\x32\x20\x32\x32\x20\x37\x2e\x38\x36\
-\x20\x32\x32\x20\x31\x36\x2e\x31\x34\x20\x31\x36\x2e\x31\x34\x20\
-\x32\x32\x20\x37\x2e\x38\x36\x20\x32\x32\x20\x32\x20\x31\x36\x2e\
-\x31\x34\x20\x32\x20\x37\x2e\x38\x36\x20\x37\x2e\x38\x36\x20\x32\
-\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\
-\x20\x20\x69\x64\x3d\x22\x6c\x69\x6e\x65\x34\x22\x0a\x20\x20\x20\
-\x20\x20\x79\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x78\
-\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\x22\
-\x38\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\x22\x20\
-\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\x20\x20\
-\x69\x64\x3d\x22\x6c\x69\x6e\x65\x36\x22\x0a\x20\x20\x20\x20\x20\
-\x79\x32\x3d\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x32\x3d\
-\x22\x31\x32\x2e\x30\x31\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\
-\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\
-\x22\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
-\x00\x00\x06\x4c\
-\x3c\
-\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
-\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x78\x6d\x6c\x6e\x73\
-\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
-\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x66\x69\
-\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x3e\x0a\x20\x3c\x67\x3e\x0a\
-\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x4c\x61\x79\x65\x72\x20\x31\
-\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
-\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3d\x22\x65\x76\x65\x6e\
-\x6f\x64\x64\x22\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3d\x22\
-\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x64\x3d\x22\x6d\x33\x33\x2e\
-\x39\x32\x35\x36\x2c\x39\x2e\x36\x39\x33\x32\x34\x63\x2d\x30\x2e\
-\x37\x36\x30\x38\x2c\x30\x20\x2d\x31\x2e\x34\x38\x30\x32\x2c\x30\
-\x2e\x33\x34\x36\x34\x33\x20\x2d\x31\x2e\x39\x35\x34\x35\x2c\x30\
-\x2e\x39\x34\x31\x32\x33\x6c\x2d\x35\x2e\x33\x32\x31\x2c\x36\x2e\
-\x36\x37\x32\x33\x6c\x2d\x31\x31\x2e\x36\x35\x30\x31\x2c\x30\x63\
-\x2d\x31\x2e\x33\x38\x30\x37\x2c\x30\x20\x2d\x32\x2e\x35\x2c\x31\
-\x2e\x31\x31\x39\x33\x20\x2d\x32\x2e\x35\x2c\x32\x2e\x35\x63\x30\
-\x2c\x31\x2e\x33\x38\x30\x37\x20\x31\x2e\x31\x31\x39\x33\x2c\x32\
-\x2e\x35\x20\x32\x2e\x35\x2c\x32\x2e\x35\x6c\x31\x2e\x35\x2c\x30\
-\x6c\x30\x2c\x34\x31\x2e\x35\x63\x30\x2c\x33\x2e\x35\x38\x39\x39\
-\x20\x32\x2e\x39\x31\x30\x31\x2c\x36\x2e\x35\x20\x36\x2e\x35\x2c\
-\x36\x2e\x35\x6c\x33\x34\x2c\x30\x63\x33\x2e\x35\x38\x39\x38\x2c\
-\x30\x20\x36\x2e\x35\x2c\x2d\x32\x2e\x39\x31\x30\x31\x20\x36\x2e\
-\x35\x2c\x2d\x36\x2e\x35\x6c\x30\x2c\x2d\x34\x31\x2e\x35\x6c\x31\
-\x2e\x35\x2c\x30\x63\x31\x2e\x33\x38\x30\x37\x2c\x30\x20\x32\x2e\
-\x35\x2c\x2d\x31\x2e\x31\x31\x39\x33\x20\x32\x2e\x35\x2c\x2d\x32\
-\x2e\x35\x63\x30\x2c\x2d\x31\x2e\x33\x38\x30\x37\x20\x2d\x31\x2e\
-\x31\x31\x39\x33\x2c\x2d\x32\x2e\x35\x20\x2d\x32\x2e\x35\x2c\x2d\
-\x32\x2e\x35\x6c\x2d\x31\x31\x2e\x36\x35\x30\x31\x2c\x30\x6c\x2d\
-\x35\x2e\x33\x32\x30\x39\x2c\x2d\x36\x2e\x36\x37\x32\x32\x63\x2d\
-\x30\x2e\x34\x37\x34\x34\x2c\x2d\x30\x2e\x35\x39\x34\x39\x20\x2d\
-\x31\x2e\x31\x39\x33\x38\x2c\x2d\x30\x2e\x39\x34\x31\x33\x33\x20\
-\x2d\x31\x2e\x39\x35\x34\x36\x2c\x2d\x30\x2e\x39\x34\x31\x33\x33\
-\x6c\x2d\x31\x32\x2e\x31\x34\x38\x38\x2c\x30\x7a\x6d\x2d\x30\x2e\
-\x39\x32\x35\x36\x2c\x31\x37\x2e\x36\x31\x33\x35\x33\x63\x31\x2e\
-\x33\x38\x30\x37\x2c\x30\x20\x32\x2e\x35\x2c\x31\x2e\x31\x31\x39\
-\x33\x20\x32\x2e\x35\x2c\x32\x2e\x35\x6c\x30\x2c\x32\x38\x63\x30\
-\x2c\x31\x2e\x33\x38\x30\x37\x20\x2d\x31\x2e\x31\x31\x39\x33\x2c\
-\x32\x2e\x35\x20\x2d\x32\x2e\x35\x2c\x32\x2e\x35\x63\x2d\x31\x2e\
-\x33\x38\x30\x37\x2c\x30\x20\x2d\x32\x2e\x35\x2c\x2d\x31\x2e\x31\
-\x31\x39\x33\x20\x2d\x32\x2e\x35\x2c\x2d\x32\x2e\x35\x6c\x30\x2c\
-\x2d\x32\x38\x63\x30\x2c\x2d\x31\x2e\x33\x38\x30\x37\x20\x31\x2e\
-\x31\x31\x39\x33\x2c\x2d\x32\x2e\x35\x20\x32\x2e\x35\x2c\x2d\x32\
-\x2e\x35\x7a\x6d\x31\x36\x2e\x35\x2c\x32\x2e\x35\x63\x30\x2c\x2d\
-\x31\x2e\x33\x38\x30\x37\x20\x2d\x31\x2e\x31\x31\x39\x33\x2c\x2d\
-\x32\x2e\x35\x20\x2d\x32\x2e\x35\x2c\x2d\x32\x2e\x35\x63\x2d\x31\
-\x2e\x33\x38\x30\x37\x2c\x30\x20\x2d\x32\x2e\x35\x2c\x31\x2e\x31\
-\x31\x39\x33\x20\x2d\x32\x2e\x35\x2c\x32\x2e\x35\x6c\x30\x2c\x32\
-\x38\x63\x30\x2c\x31\x2e\x33\x38\x30\x37\x20\x31\x2e\x31\x31\x39\
-\x33\x2c\x32\x2e\x35\x20\x32\x2e\x35\x2c\x32\x2e\x35\x63\x31\x2e\
-\x33\x38\x30\x37\x2c\x30\x20\x32\x2e\x35\x2c\x2d\x31\x2e\x31\x31\
-\x39\x33\x20\x32\x2e\x35\x2c\x2d\x32\x2e\x35\x6c\x30\x2c\x2d\x32\
-\x38\x7a\x6d\x2d\x32\x2e\x35\x34\x36\x34\x2c\x2d\x31\x32\x2e\x35\
-\x30\x31\x34\x6c\x2d\x32\x2e\x30\x38\x33\x32\x2c\x2d\x32\x2e\x36\
-\x31\x32\x31\x6c\x2d\x39\x2e\x37\x34\x30\x38\x2c\x30\x6c\x2d\x32\
-\x2e\x30\x38\x33\x32\x2c\x32\x2e\x36\x31\x32\x31\x6c\x31\x33\x2e\
-\x39\x30\x37\x32\x2c\x30\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\
-\x43\x32\x43\x43\x44\x45\x22\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\
-\x31\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\
-\x6c\x3d\x22\x23\x66\x66\x30\x30\x30\x30\x22\x20\x64\x3d\x22\x6d\
-\x37\x2e\x33\x33\x38\x33\x2c\x33\x39\x2e\x39\x39\x39\x39\x34\x6c\
-\x30\x2c\x30\x63\x30\x2c\x2d\x31\x38\x2e\x30\x38\x35\x33\x35\x20\
-\x31\x34\x2e\x36\x32\x33\x31\x36\x2c\x2d\x33\x32\x2e\x37\x34\x36\
-\x35\x31\x20\x33\x32\x2e\x36\x36\x31\x36\x37\x2c\x2d\x33\x32\x2e\
-\x37\x34\x36\x35\x31\x6c\x30\x2c\x30\x63\x38\x2e\x36\x36\x32\x35\
-\x33\x2c\x30\x20\x31\x36\x2e\x39\x37\x30\x32\x35\x2c\x33\x2e\x34\
-\x35\x30\x30\x38\x20\x32\x33\x2e\x30\x39\x35\x33\x32\x2c\x39\x2e\
-\x35\x39\x31\x32\x36\x63\x36\x2e\x31\x32\x35\x33\x31\x2c\x36\x2e\
-\x31\x34\x31\x31\x39\x20\x39\x2e\x35\x36\x36\x34\x32\x2c\x31\x34\
-\x2e\x34\x37\x30\x34\x34\x20\x39\x2e\x35\x36\x36\x34\x32\x2c\x32\
-\x33\x2e\x31\x35\x35\x32\x34\x6c\x30\x2c\x30\x63\x30\x2c\x31\x38\
-\x2e\x30\x38\x35\x35\x36\x20\x2d\x31\x34\x2e\x36\x32\x33\x30\x35\
-\x2c\x33\x32\x2e\x37\x34\x36\x36\x33\x20\x2d\x33\x32\x2e\x36\x36\
-\x31\x37\x33\x2c\x33\x32\x2e\x37\x34\x36\x36\x33\x6c\x30\x2c\x30\
-\x63\x2d\x31\x38\x2e\x30\x33\x38\x35\x31\x2c\x30\x20\x2d\x33\x32\
-\x2e\x36\x36\x31\x36\x37\x2c\x2d\x31\x34\x2e\x36\x36\x31\x30\x36\
-\x20\x2d\x33\x32\x2e\x36\x36\x31\x36\x37\x2c\x2d\x33\x32\x2e\x37\
-\x34\x36\x36\x33\x63\x30\x2c\x30\x20\x30\x2c\x30\x20\x30\x2c\x30\
-\x6c\x2d\x30\x2e\x30\x30\x30\x30\x31\x2c\x30\x7a\x6d\x35\x32\x2e\
-\x37\x34\x31\x32\x31\x2c\x31\x34\x2e\x36\x34\x39\x31\x36\x6c\x30\
-\x2c\x30\x63\x37\x2e\x31\x39\x31\x30\x32\x2c\x2d\x39\x2e\x39\x30\
-\x38\x33\x34\x20\x36\x2e\x31\x32\x32\x30\x39\x2c\x2d\x32\x33\x2e\
-\x35\x38\x39\x34\x38\x20\x2d\x32\x2e\x35\x32\x30\x31\x35\x2c\x2d\
-\x33\x32\x2e\x32\x35\x34\x30\x37\x63\x2d\x38\x2e\x36\x34\x32\x32\
-\x35\x2c\x2d\x38\x2e\x36\x36\x34\x36\x36\x20\x2d\x32\x32\x2e\x32\
-\x38\x37\x39\x33\x2c\x2d\x39\x2e\x37\x33\x36\x33\x37\x20\x2d\x33\
-\x32\x2e\x31\x37\x30\x33\x33\x2c\x2d\x32\x2e\x35\x32\x36\x35\x38\
-\x6c\x33\x34\x2e\x36\x39\x30\x34\x37\x2c\x33\x34\x2e\x37\x38\x30\
-\x36\x35\x6c\x30\x2e\x30\x30\x30\x30\x31\x2c\x30\x7a\x6d\x2d\x34\
-\x30\x2e\x31\x35\x38\x38\x39\x2c\x2d\x32\x39\x2e\x32\x39\x38\x30\
-\x31\x63\x2d\x37\x2e\x31\x39\x31\x30\x37\x2c\x39\x2e\x39\x30\x38\
-\x32\x38\x20\x2d\x36\x2e\x31\x32\x32\x31\x37\x2c\x32\x33\x2e\x35\
-\x38\x39\x34\x33\x20\x32\x2e\x35\x32\x30\x30\x32\x2c\x33\x32\x2e\
-\x32\x35\x33\x39\x63\x38\x2e\x36\x34\x32\x31\x37\x2c\x38\x2e\x36\
-\x36\x34\x37\x31\x20\x32\x32\x2e\x32\x38\x37\x38\x35\x2c\x39\x2e\
-\x37\x33\x36\x34\x31\x20\x33\x32\x2e\x31\x37\x30\x32\x35\x2c\x32\
-\x2e\x35\x32\x36\x37\x6c\x2d\x33\x34\x2e\x36\x39\x30\x32\x38\x2c\
-\x2d\x33\x34\x2e\x37\x38\x30\x35\x39\x6c\x30\x2c\x30\x6c\x30\x2e\
-\x30\x30\x30\x30\x31\x2c\x2d\x30\x2e\x30\x30\x30\x30\x31\x7a\x22\
-\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x33\x22\x2f\x3e\x0a\x20\x3c\
-\x2f\x67\x3e\x0a\x0a\x3c\x2f\x73\x76\x67\x3e\
-\x00\x00\x09\x97\
-\x3c\
-\x73\x76\x67\x20\x66\x69\x6c\x6c\x3d\x22\x23\x30\x30\x30\x30\x30\
-\x30\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\
-\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\
-\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
-\x20\x30\x20\x35\x30\x20\x35\x30\x22\x20\x77\x69\x64\x74\x68\x3d\
-\x22\x35\x30\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\
-\x30\x70\x78\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x20\
-\x32\x35\x20\x32\x20\x43\x20\x32\x30\x2e\x39\x34\x31\x34\x30\x36\
-\x20\x32\x20\x31\x38\x2e\x31\x38\x37\x35\x20\x32\x2e\x39\x36\x38\
-\x37\x35\x20\x31\x36\x2e\x34\x33\x37\x35\x20\x34\x2e\x33\x37\x35\
-\x20\x43\x20\x31\x34\x2e\x36\x38\x37\x35\x20\x35\x2e\x37\x38\x31\
-\x32\x35\x20\x31\x34\x20\x37\x2e\x35\x38\x39\x38\x34\x34\x20\x31\
-\x34\x20\x39\x2e\x30\x39\x33\x37\x35\x20\x4c\x20\x31\x34\x20\x31\
-\x34\x20\x4c\x20\x32\x34\x20\x31\x34\x20\x4c\x20\x32\x34\x20\x31\
-\x35\x20\x4c\x20\x39\x2e\x30\x39\x33\x37\x35\x20\x31\x35\x20\x43\
-\x20\x37\x2e\x32\x36\x35\x36\x32\x35\x20\x31\x35\x20\x35\x2e\x34\
-\x31\x30\x31\x35\x36\x20\x31\x35\x2e\x37\x39\x32\x39\x36\x39\x20\
-\x34\x2e\x30\x39\x33\x37\x35\x20\x31\x37\x2e\x34\x36\x38\x37\x35\
-\x20\x43\x20\x32\x2e\x37\x37\x37\x33\x34\x34\x20\x31\x39\x2e\x31\
-\x34\x34\x35\x33\x31\x20\x32\x20\x32\x31\x2e\x36\x34\x34\x35\x33\
-\x31\x20\x32\x20\x32\x35\x20\x43\x20\x32\x20\x32\x38\x2e\x33\x35\
-\x35\x34\x36\x39\x20\x32\x2e\x37\x37\x37\x33\x34\x34\x20\x33\x30\
-\x2e\x38\x35\x35\x34\x36\x39\x20\x34\x2e\x30\x39\x33\x37\x35\x20\
-\x33\x32\x2e\x35\x33\x31\x32\x35\x20\x43\x20\x35\x2e\x34\x31\x30\
-\x31\x35\x36\x20\x33\x34\x2e\x32\x30\x37\x30\x33\x31\x20\x37\x2e\
-\x32\x36\x35\x36\x32\x35\x20\x33\x35\x20\x39\x2e\x30\x39\x33\x37\
-\x35\x20\x33\x35\x20\x4c\x20\x31\x34\x20\x33\x35\x20\x4c\x20\x31\
-\x34\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x43\x20\x31\x34\x20\
-\x34\x32\x2e\x34\x31\x30\x31\x35\x36\x20\x31\x34\x2e\x36\x38\x37\
-\x35\x20\x34\x34\x2e\x32\x31\x38\x37\x35\x20\x31\x36\x2e\x34\x33\
-\x37\x35\x20\x34\x35\x2e\x36\x32\x35\x20\x43\x20\x31\x38\x2e\x31\
-\x38\x37\x35\x20\x34\x37\x2e\x30\x33\x31\x32\x35\x20\x32\x30\x2e\
-\x39\x34\x31\x34\x30\x36\x20\x34\x38\x20\x32\x35\x20\x34\x38\x20\
-\x43\x20\x32\x39\x2e\x30\x35\x38\x35\x39\x34\x20\x34\x38\x20\x33\
-\x31\x2e\x38\x31\x32\x35\x20\x34\x37\x2e\x30\x33\x31\x32\x35\x20\
-\x33\x33\x2e\x35\x36\x32\x35\x20\x34\x35\x2e\x36\x32\x35\x20\x43\
-\x20\x33\x35\x2e\x33\x31\x32\x35\x20\x34\x34\x2e\x32\x31\x38\x37\
-\x35\x20\x33\x36\x20\x34\x32\x2e\x34\x31\x30\x31\x35\x36\x20\x33\
-\x36\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x4c\x20\x33\x36\x20\
-\x33\x36\x20\x4c\x20\x32\x36\x20\x33\x36\x20\x4c\x20\x32\x36\x20\
-\x33\x35\x20\x4c\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x33\x35\
-\x20\x43\x20\x34\x32\x2e\x37\x33\x34\x33\x37\x35\x20\x33\x35\x20\
-\x34\x34\x2e\x35\x38\x39\x38\x34\x34\x20\x33\x34\x2e\x32\x30\x37\
-\x30\x33\x31\x20\x34\x35\x2e\x39\x30\x36\x32\x35\x20\x33\x32\x2e\
-\x35\x33\x31\x32\x35\x20\x43\x20\x34\x37\x2e\x32\x32\x32\x36\x35\
-\x36\x20\x33\x30\x2e\x38\x35\x35\x34\x36\x39\x20\x34\x38\x20\x32\
-\x38\x2e\x33\x35\x35\x34\x36\x39\x20\x34\x38\x20\x32\x35\x20\x43\
-\x20\x34\x38\x20\x32\x31\x2e\x36\x34\x34\x35\x33\x31\x20\x34\x37\
-\x2e\x32\x32\x32\x36\x35\x36\x20\x31\x39\x2e\x31\x34\x34\x35\x33\
-\x31\x20\x34\x35\x2e\x39\x30\x36\x32\x35\x20\x31\x37\x2e\x34\x36\
-\x38\x37\x35\x20\x43\x20\x34\x34\x2e\x35\x38\x39\x38\x34\x34\x20\
-\x31\x35\x2e\x37\x39\x32\x39\x36\x39\x20\x34\x32\x2e\x37\x33\x34\
-\x33\x37\x35\x20\x31\x35\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\
-\x31\x35\x20\x4c\x20\x33\x36\x20\x31\x35\x20\x4c\x20\x33\x36\x20\
-\x39\x2e\x30\x39\x33\x37\x35\x20\x43\x20\x33\x36\x20\x37\x2e\x35\
-\x35\x30\x37\x38\x31\x20\x33\x35\x2e\x33\x31\x36\x34\x30\x36\x20\
-\x35\x2e\x37\x33\x38\x32\x38\x31\x20\x33\x33\x2e\x35\x36\x32\x35\
-\x20\x34\x2e\x33\x34\x33\x37\x35\x20\x43\x20\x33\x31\x2e\x38\x30\
-\x38\x35\x39\x34\x20\x32\x2e\x39\x34\x39\x32\x31\x39\x20\x32\x39\
-\x2e\x30\x35\x34\x36\x38\x38\x20\x32\x20\x32\x35\x20\x32\x20\x5a\
-\x20\x4d\x20\x32\x35\x20\x34\x20\x43\x20\x32\x38\x2e\x37\x34\x36\
-\x30\x39\x34\x20\x34\x20\x33\x31\x2e\x30\x31\x35\x36\x32\x35\x20\
-\x34\x2e\x38\x37\x35\x20\x33\x32\x2e\x33\x31\x32\x35\x20\x35\x2e\
-\x39\x30\x36\x32\x35\x20\x43\x20\x33\x33\x2e\x36\x30\x39\x33\x37\
-\x35\x20\x36\x2e\x39\x33\x37\x35\x20\x33\x34\x20\x38\x2e\x31\x33\
-\x36\x37\x31\x39\x20\x33\x34\x20\x39\x2e\x30\x39\x33\x37\x35\x20\
-\x4c\x20\x33\x34\x20\x32\x31\x20\x43\x20\x33\x34\x20\x32\x32\x2e\
-\x36\x35\x36\x32\x35\x20\x33\x32\x2e\x36\x35\x36\x32\x35\x20\x32\
-\x34\x20\x33\x31\x20\x32\x34\x20\x4c\x20\x31\x39\x20\x32\x34\x20\
-\x43\x20\x31\x36\x2e\x39\x34\x31\x34\x30\x36\x20\x32\x34\x20\x31\
-\x35\x2e\x31\x36\x37\x39\x36\x39\x20\x32\x35\x2e\x32\x36\x39\x35\
-\x33\x31\x20\x31\x34\x2e\x34\x30\x36\x32\x35\x20\x32\x37\x2e\x30\
-\x36\x32\x35\x20\x43\x20\x31\x34\x2e\x32\x37\x37\x33\x34\x34\x20\
-\x32\x37\x2e\x33\x35\x39\x33\x37\x35\x20\x31\x34\x2e\x31\x36\x30\
-\x31\x35\x36\x20\x32\x37\x2e\x36\x37\x35\x37\x38\x31\x20\x31\x34\
-\x2e\x30\x39\x33\x37\x35\x20\x32\x38\x20\x43\x20\x31\x34\x2e\x30\
-\x32\x37\x33\x34\x34\x20\x32\x38\x2e\x33\x32\x34\x32\x31\x39\x20\
-\x31\x34\x20\x32\x38\x2e\x36\x35\x36\x32\x35\x20\x31\x34\x20\x32\
-\x39\x20\x4c\x20\x31\x34\x20\x33\x33\x20\x4c\x20\x39\x2e\x30\x39\
-\x33\x37\x35\x20\x33\x33\x20\x43\x20\x37\x2e\x38\x32\x34\x32\x31\
-\x39\x20\x33\x33\x20\x36\x2e\x36\x34\x38\x34\x33\x38\x20\x33\x32\
-\x2e\x35\x30\x33\x39\x30\x36\x20\x35\x2e\x36\x38\x37\x35\x20\x33\
-\x31\x2e\x32\x38\x31\x32\x35\x20\x43\x20\x34\x2e\x37\x32\x36\x35\
-\x36\x33\x20\x33\x30\x2e\x30\x35\x38\x35\x39\x34\x20\x34\x20\x32\
-\x38\x2e\x30\x34\x32\x39\x36\x39\x20\x34\x20\x32\x35\x20\x43\x20\
-\x34\x20\x32\x31\x2e\x39\x35\x37\x30\x33\x31\x20\x34\x2e\x37\x32\
-\x36\x35\x36\x33\x20\x31\x39\x2e\x39\x34\x31\x34\x30\x36\x20\x35\
-\x2e\x36\x38\x37\x35\x20\x31\x38\x2e\x37\x31\x38\x37\x35\x20\x43\
-\x20\x36\x2e\x36\x34\x38\x34\x33\x38\x20\x31\x37\x2e\x34\x39\x36\
-\x30\x39\x34\x20\x37\x2e\x38\x32\x34\x32\x31\x39\x20\x31\x37\x20\
-\x39\x2e\x30\x39\x33\x37\x35\x20\x31\x37\x20\x4c\x20\x32\x36\x20\
-\x31\x37\x20\x4c\x20\x32\x36\x20\x31\x32\x20\x4c\x20\x31\x36\x20\
-\x31\x32\x20\x4c\x20\x31\x36\x20\x39\x2e\x30\x39\x33\x37\x35\x20\
-\x43\x20\x31\x36\x20\x38\x2e\x31\x39\x39\x32\x31\x39\x20\x31\x36\
-\x2e\x33\x38\x36\x37\x31\x39\x20\x36\x2e\x39\x38\x30\x34\x36\x39\
-\x20\x31\x37\x2e\x36\x38\x37\x35\x20\x35\x2e\x39\x33\x37\x35\x20\
-\x43\x20\x31\x38\x2e\x39\x38\x38\x32\x38\x31\x20\x34\x2e\x38\x39\
-\x34\x35\x33\x31\x20\x32\x31\x2e\x32\x35\x37\x38\x31\x33\x20\x34\
-\x20\x32\x35\x20\x34\x20\x5a\x20\x4d\x20\x32\x30\x20\x37\x20\x43\
-\x20\x31\x38\x2e\x38\x39\x38\x34\x33\x38\x20\x37\x20\x31\x38\x20\
-\x37\x2e\x38\x39\x38\x34\x33\x38\x20\x31\x38\x20\x39\x20\x43\x20\
-\x31\x38\x20\x31\x30\x2e\x31\x30\x31\x35\x36\x33\x20\x31\x38\x2e\
-\x38\x39\x38\x34\x33\x38\x20\x31\x31\x20\x32\x30\x20\x31\x31\x20\
-\x43\x20\x32\x31\x2e\x31\x30\x31\x35\x36\x33\x20\x31\x31\x20\x32\
-\x32\x20\x31\x30\x2e\x31\x30\x31\x35\x36\x33\x20\x32\x32\x20\x39\
-\x20\x43\x20\x32\x32\x20\x37\x2e\x38\x39\x38\x34\x33\x38\x20\x32\
-\x31\x2e\x31\x30\x31\x35\x36\x33\x20\x37\x20\x32\x30\x20\x37\x20\
-\x5a\x20\x4d\x20\x33\x36\x20\x31\x37\x20\x4c\x20\x34\x30\x2e\x39\
-\x30\x36\x32\x35\x20\x31\x37\x20\x43\x20\x34\x32\x2e\x31\x37\x35\
-\x37\x38\x31\x20\x31\x37\x20\x34\x33\x2e\x33\x35\x31\x35\x36\x33\
-\x20\x31\x37\x2e\x34\x39\x36\x30\x39\x34\x20\x34\x34\x2e\x33\x31\
-\x32\x35\x20\x31\x38\x2e\x37\x31\x38\x37\x35\x20\x43\x20\x34\x35\
-\x2e\x32\x37\x33\x34\x33\x38\x20\x31\x39\x2e\x39\x34\x31\x34\x30\
-\x36\x20\x34\x36\x20\x32\x31\x2e\x39\x35\x37\x30\x33\x31\x20\x34\
-\x36\x20\x32\x35\x20\x43\x20\x34\x36\x20\x32\x38\x2e\x30\x34\x32\
-\x39\x36\x39\x20\x34\x35\x2e\x32\x37\x33\x34\x33\x38\x20\x33\x30\
-\x2e\x30\x35\x38\x35\x39\x34\x20\x34\x34\x2e\x33\x31\x32\x35\x20\
-\x33\x31\x2e\x32\x38\x31\x32\x35\x20\x43\x20\x34\x33\x2e\x33\x35\
-\x31\x35\x36\x33\x20\x33\x32\x2e\x35\x30\x33\x39\x30\x36\x20\x34\
-\x32\x2e\x31\x37\x35\x37\x38\x31\x20\x33\x33\x20\x34\x30\x2e\x39\
-\x30\x36\x32\x35\x20\x33\x33\x20\x4c\x20\x32\x34\x20\x33\x33\x20\
-\x4c\x20\x32\x34\x20\x33\x38\x20\x4c\x20\x33\x34\x20\x33\x38\x20\
-\x4c\x20\x33\x34\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x43\x20\
-\x33\x34\x20\x34\x31\x2e\x38\x30\x30\x37\x38\x31\x20\x33\x33\x2e\
-\x36\x31\x33\x32\x38\x31\x20\x34\x33\x2e\x30\x31\x39\x35\x33\x31\
-\x20\x33\x32\x2e\x33\x31\x32\x35\x20\x34\x34\x2e\x30\x36\x32\x35\
-\x20\x43\x20\x33\x31\x2e\x30\x31\x31\x37\x31\x39\x20\x34\x35\x2e\
-\x31\x30\x35\x34\x36\x39\x20\x32\x38\x2e\x37\x34\x32\x31\x38\x38\
-\x20\x34\x36\x20\x32\x35\x20\x34\x36\x20\x43\x20\x32\x31\x2e\x32\
-\x35\x37\x38\x31\x33\x20\x34\x36\x20\x31\x38\x2e\x39\x38\x38\x32\
-\x38\x31\x20\x34\x35\x2e\x31\x30\x35\x34\x36\x39\x20\x31\x37\x2e\
-\x36\x38\x37\x35\x20\x34\x34\x2e\x30\x36\x32\x35\x20\x43\x20\x31\
-\x36\x2e\x33\x38\x36\x37\x31\x39\x20\x34\x33\x2e\x30\x31\x39\x35\
-\x33\x31\x20\x31\x36\x20\x34\x31\x2e\x38\x30\x30\x37\x38\x31\x20\
-\x31\x36\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x4c\x20\x31\x36\
-\x20\x32\x39\x20\x43\x20\x31\x36\x20\x32\x38\x2e\x37\x39\x32\x39\
-\x36\x39\x20\x31\x36\x2e\x30\x32\x33\x34\x33\x38\x20\x32\x38\x2e\
-\x36\x30\x31\x35\x36\x33\x20\x31\x36\x2e\x30\x36\x32\x35\x20\x32\
-\x38\x2e\x34\x30\x36\x32\x35\x20\x43\x20\x31\x36\x2e\x33\x34\x33\
-\x37\x35\x20\x32\x37\x2e\x30\x33\x39\x30\x36\x33\x20\x31\x37\x2e\
-\x35\x35\x30\x37\x38\x31\x20\x32\x36\x20\x31\x39\x20\x32\x36\x20\
-\x4c\x20\x33\x31\x20\x32\x36\x20\x43\x20\x33\x33\x2e\x37\x34\x36\
-\x30\x39\x34\x20\x32\x36\x20\x33\x36\x20\x32\x33\x2e\x37\x34\x36\
-\x30\x39\x34\x20\x33\x36\x20\x32\x31\x20\x5a\x20\x4d\x20\x33\x30\
-\x20\x33\x39\x20\x43\x20\x32\x38\x2e\x38\x39\x38\x34\x33\x38\x20\
-\x33\x39\x20\x32\x38\x20\x33\x39\x2e\x38\x39\x38\x34\x33\x38\x20\
-\x32\x38\x20\x34\x31\x20\x43\x20\x32\x38\x20\x34\x32\x2e\x31\x30\
-\x31\x35\x36\x33\x20\x32\x38\x2e\x38\x39\x38\x34\x33\x38\x20\x34\
-\x33\x20\x33\x30\x20\x34\x33\x20\x43\x20\x33\x31\x2e\x31\x30\x31\
-\x35\x36\x33\x20\x34\x33\x20\x33\x32\x20\x34\x32\x2e\x31\x30\x31\
-\x35\x36\x33\x20\x33\x32\x20\x34\x31\x20\x43\x20\x33\x32\x20\x33\
-\x39\x2e\x38\x39\x38\x34\x33\x38\x20\x33\x31\x2e\x31\x30\x31\x35\
-\x36\x33\x20\x33\x39\x20\x33\x30\x20\x33\x39\x20\x5a\x22\x2f\x3e\
-\x3c\x2f\x73\x76\x67\x3e\
-"
-
-qt_resource_name = b"\
-\x00\x09\
-\x00\x28\xbf\x23\
-\x00\x73\
-\x00\x74\x00\x79\x00\x6c\x00\x65\x00\x2e\x00\x63\x00\x73\x00\x73\
-\x00\x05\
-\x00\x6f\xa6\x53\
-\x00\x69\
-\x00\x63\x00\x6f\x00\x6e\x00\x73\
-\x00\x0f\
-\x00\x50\xd7\x47\
-\x00\x70\
-\x00\x6c\x00\x75\x00\x73\x00\x2d\x00\x73\x00\x71\x00\x75\x00\x61\x00\x72\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0c\
-\x02\x33\x2a\x87\
-\x00\x6e\
-\x00\x6f\x00\x2d\x00\x61\x00\x6c\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x10\
-\x03\xdc\xdd\x87\
-\x00\x73\
-\x00\x74\x00\x61\x00\x72\x00\x2d\x00\x63\x00\x72\x00\x6f\x00\x73\x00\x73\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x08\
-\x05\x77\x54\xa7\
-\x00\x6c\
-\x00\x6f\x00\x61\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x08\
-\x05\xa8\x57\x87\
-\x00\x63\
-\x00\x6f\x00\x64\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0a\
-\x08\x4a\xc4\x07\
-\x00\x65\
-\x00\x78\x00\x70\x00\x61\x00\x6e\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x09\
-\x08\x9b\xad\xc7\
-\x00\x74\
-\x00\x72\x00\x61\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x08\
-\x08\xc8\x55\xe7\
-\x00\x73\
-\x00\x61\x00\x76\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x07\
-\x09\xc7\x5a\x27\
-\x00\x73\
-\x00\x65\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x08\
-\x0a\x85\x55\x87\
-\x00\x73\
-\x00\x74\x00\x61\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0a\
-\x0a\xc8\xf6\x87\
-\x00\x66\
-\x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0c\
-\x0a\xdc\x3f\xc7\
-\x00\x63\
-\x00\x6f\x00\x6c\x00\x6c\x00\x61\x00\x70\x00\x73\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0f\
-\x0b\x14\x80\xa7\
-\x00\x67\
-\x00\x72\x00\x65\x00\x65\x00\x6e\x00\x2d\x00\x61\x00\x6c\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0b\
-\x0c\x6a\x21\xc7\
-\x00\x72\
-\x00\x65\x00\x66\x00\x72\x00\x65\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0a\
-\x0c\xad\x02\x87\
-\x00\x64\
-\x00\x65\x00\x6c\x00\x65\x00\x74\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x0d\
-\x0d\xf9\x2b\x67\
-\x00\x72\
-\x00\x65\x00\x64\x00\x2d\x00\x61\x00\x6c\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
-\x00\x11\
-\x0e\x2c\x55\xe7\
-\x00\x74\
-\x00\x72\x00\x61\x00\x73\x00\x68\x00\x2d\x00\x63\x00\x72\x00\x6f\x00\x73\x00\x73\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
-\
-\x00\x0a\
-\x0f\x6e\x5b\x87\
-\x00\x70\
-\x00\x79\x00\x74\x00\x68\x00\x6f\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\
-"
-
-qt_resource_struct_v1 = b"\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\
-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x00\x18\x00\x02\x00\x00\x00\x12\x00\x00\x00\x03\
-\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\
-\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x01\x7d\
-\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x21\
-\x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xfe\
-\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x6e\
-\x00\x00\x00\xbc\x00\x01\x00\x00\x00\x01\x00\x00\x0d\xa5\
-\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x11\x51\
-\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf0\
-\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00\x16\x7c\
-\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x17\xf0\
-\x00\x00\x01\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xae\
-\x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x22\x74\
-\x00\x00\x01\x66\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x07\
-\x00\x00\x01\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x33\xa8\
-\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x35\x3c\
-\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x36\xb6\
-\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x57\
-\x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x44\xa7\
-"
-
-qt_resource_struct_v2 = b"\
-\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\
-\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
-\x00\x00\x00\x18\x00\x02\x00\x00\x00\x12\x00\x00\x00\x03\
-\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x01\x7d\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
-\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x21\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-\x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xfe\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x6e\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
-\x00\x00\x00\xbc\x00\x01\x00\x00\x00\x01\x00\x00\x0d\xa5\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
-\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x11\x51\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
-\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf0\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00\x16\x7c\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x17\xf0\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
-\x00\x00\x01\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xae\
-\x00\x00\x01\x9a\x4b\xc3\x1d\x94\
-\x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x22\x74\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
-\x00\x00\x01\x66\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x07\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd2\
-\x00\x00\x01\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x33\xa8\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x35\x3c\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
-\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x36\xb6\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
-\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x57\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
-\x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x44\xa7\
-\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
-"
-
-qt_version = [int(v) for v in QtCore.qVersion().split('.')]
-if qt_version < [5, 8, 0]:
- rcc_version = 1
- qt_resource_struct = qt_resource_struct_v1
-else:
- rcc_version = 2
- qt_resource_struct = qt_resource_struct_v2
-
-def qInitResources():
- QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
-
-def qCleanupResources():
- QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data)
-
-qInitResources()
+# -*- coding: utf-8 -*-
+
+# Resource object code
+#
+# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt5 import QtCore
+
+qt_resource_data = b"\
+\x00\x00\x00\x00\
+\
+\x00\x00\x01\x75\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x70\x6c\x75\x73\x2d\
+\x73\x71\x75\x61\x72\x65\x22\x3e\x3c\x72\x65\x63\x74\x20\x78\x3d\
+\x22\x33\x22\x20\x79\x3d\x22\x33\x22\x20\x77\x69\x64\x74\x68\x3d\
+\x22\x31\x38\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x31\x38\x22\
+\x20\x72\x78\x3d\x22\x32\x22\x20\x72\x79\x3d\x22\x32\x22\x3e\x3c\
+\x2f\x72\x65\x63\x74\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\x22\
+\x31\x32\x22\x20\x79\x31\x3d\x22\x38\x22\x20\x78\x32\x3d\x22\x31\
+\x32\x22\x20\x79\x32\x3d\x22\x31\x36\x22\x3e\x3c\x2f\x6c\x69\x6e\
+\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\x22\x38\x22\x20\x79\
+\x31\x3d\x22\x31\x32\x22\x20\x78\x32\x3d\x22\x31\x36\x22\x20\x79\
+\x32\x3d\x22\x31\x32\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x2f\
+\x73\x76\x67\x3e\
+\x00\x00\x01\xa0\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x61\x6c\x65\x72\x74\
+\x2d\x6f\x63\x74\x61\x67\x6f\x6e\x22\x3e\x3c\x70\x6f\x6c\x79\x67\
+\x6f\x6e\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x2e\x38\x36\x20\
+\x32\x20\x31\x36\x2e\x31\x34\x20\x32\x20\x32\x32\x20\x37\x2e\x38\
+\x36\x20\x32\x32\x20\x31\x36\x2e\x31\x34\x20\x31\x36\x2e\x31\x34\
+\x20\x32\x32\x20\x37\x2e\x38\x36\x20\x32\x32\x20\x32\x20\x31\x36\
+\x2e\x31\x34\x20\x32\x20\x37\x2e\x38\x36\x20\x37\x2e\x38\x36\x20\
+\x32\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x67\x6f\x6e\x3e\x3c\x6c\x69\
+\x6e\x65\x20\x78\x31\x3d\x22\x31\x32\x22\x20\x79\x31\x3d\x22\x38\
+\x22\x20\x78\x32\x3d\x22\x31\x32\x22\x20\x79\x32\x3d\x22\x31\x32\
+\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\
+\x31\x3d\x22\x31\x32\x22\x20\x79\x31\x3d\x22\x31\x36\x22\x20\x78\
+\x32\x3d\x22\x31\x32\x2e\x30\x31\x22\x20\x79\x32\x3d\x22\x31\x36\
+\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x07\xd9\
+\x3c\
+\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
+\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x78\x6d\x6c\x6e\x73\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x66\x69\
+\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x3e\x0a\x20\x3c\x67\x3e\x0a\
+\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x4c\x61\x79\x65\x72\x20\x31\
+\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x20\x64\x3d\x22\x6d\x33\x38\x2e\x31\x34\x34\x31\x2c\x31\x35\x2e\
+\x31\x36\x31\x36\x31\x63\x30\x2e\x36\x37\x31\x38\x2c\x2d\x31\x2e\
+\x36\x37\x32\x39\x20\x33\x2e\x30\x34\x2c\x2d\x31\x2e\x36\x37\x32\
+\x39\x20\x33\x2e\x37\x31\x31\x38\x2c\x30\x6c\x35\x2e\x39\x35\x32\
+\x32\x2c\x31\x34\x2e\x38\x32\x32\x32\x63\x30\x2e\x32\x38\x36\x31\
+\x2c\x30\x2e\x37\x31\x32\x34\x20\x30\x2e\x39\x35\x34\x37\x2c\x31\
+\x2e\x31\x39\x38\x31\x20\x31\x2e\x37\x32\x30\x37\x2c\x31\x2e\x32\
+\x35\x30\x31\x6c\x31\x35\x2e\x39\x33\x36\x2c\x31\x2e\x30\x38\x30\
+\x35\x63\x31\x2e\x37\x39\x38\x37\x2c\x30\x2e\x31\x32\x32\x20\x32\
+\x2e\x35\x33\x30\x35\x2c\x32\x2e\x33\x37\x34\x34\x20\x31\x2e\x31\
+\x34\x37\x2c\x33\x2e\x35\x33\x30\x32\x6c\x2d\x31\x32\x2e\x32\x35\
+\x37\x34\x2c\x31\x30\x2e\x32\x34\x31\x32\x63\x2d\x30\x2e\x35\x38\
+\x39\x31\x2c\x30\x2e\x34\x39\x32\x32\x20\x2d\x30\x2e\x38\x34\x34\
+\x35\x2c\x31\x2e\x32\x37\x38\x32\x20\x2d\x30\x2e\x36\x35\x37\x32\
+\x2c\x32\x2e\x30\x32\x32\x37\x6c\x33\x2e\x38\x39\x36\x39\x2c\x31\
+\x35\x2e\x34\x39\x63\x30\x2e\x34\x33\x39\x38\x2c\x31\x2e\x37\x34\
+\x38\x33\x20\x2d\x31\x2e\x34\x37\x36\x32\x2c\x33\x2e\x31\x34\x30\
+\x34\x20\x2d\x33\x2e\x30\x30\x33\x2c\x32\x2e\x31\x38\x31\x38\x6c\
+\x2d\x31\x33\x2e\x35\x32\x37\x37\x2c\x2d\x38\x2e\x34\x39\x32\x38\
+\x63\x2d\x30\x2e\x36\x35\x30\x32\x2c\x2d\x30\x2e\x34\x30\x38\x32\
+\x20\x2d\x31\x2e\x34\x37\x36\x36\x2c\x2d\x30\x2e\x34\x30\x38\x32\
+\x20\x2d\x32\x2e\x31\x32\x36\x38\x2c\x30\x6c\x2d\x31\x33\x2e\x35\
+\x32\x37\x37\x2c\x38\x2e\x34\x39\x32\x38\x63\x2d\x31\x2e\x35\x32\
+\x36\x38\x2c\x30\x2e\x39\x35\x38\x36\x20\x2d\x33\x2e\x34\x34\x32\
+\x38\x2c\x2d\x30\x2e\x34\x33\x33\x35\x20\x2d\x33\x2e\x30\x30\x33\
+\x2c\x2d\x32\x2e\x31\x38\x31\x38\x6c\x33\x2e\x38\x39\x36\x39\x2c\
+\x2d\x31\x35\x2e\x34\x39\x63\x30\x2e\x31\x38\x37\x33\x2c\x2d\x30\
+\x2e\x37\x34\x34\x35\x20\x2d\x30\x2e\x30\x36\x38\x31\x2c\x2d\x31\
+\x2e\x35\x33\x30\x35\x20\x2d\x30\x2e\x36\x35\x37\x32\x2c\x2d\x32\
+\x2e\x30\x32\x32\x37\x6c\x2d\x31\x32\x2e\x32\x35\x37\x34\x2c\x2d\
+\x31\x30\x2e\x32\x34\x31\x32\x63\x2d\x31\x2e\x33\x38\x33\x35\x2c\
+\x2d\x31\x2e\x31\x35\x35\x38\x20\x2d\x30\x2e\x36\x35\x31\x37\x2c\
+\x2d\x33\x2e\x34\x30\x38\x32\x20\x31\x2e\x31\x34\x37\x2c\x2d\x33\
+\x2e\x35\x33\x30\x32\x6c\x31\x35\x2e\x39\x33\x36\x2c\x2d\x31\x2e\
+\x30\x38\x30\x35\x63\x30\x2e\x37\x36\x36\x2c\x2d\x30\x2e\x30\x35\
+\x32\x20\x31\x2e\x34\x33\x34\x36\x2c\x2d\x30\x2e\x35\x33\x37\x37\
+\x20\x31\x2e\x37\x32\x30\x37\x2c\x2d\x31\x2e\x32\x35\x30\x31\x6c\
+\x35\x2e\x39\x35\x32\x32\x2c\x2d\x31\x34\x2e\x38\x32\x32\x32\x7a\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\x39\x39\x34\x41\x22\
+\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x31\x22\x2f\x3e\x0a\x20\x20\
+\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x6d\x33\x39\x2e\x30\x35\x31\
+\x35\x2c\x32\x36\x2e\x33\x31\x30\x38\x63\x30\x2e\x33\x33\x35\x39\
+\x2c\x2d\x30\x2e\x38\x33\x36\x34\x20\x31\x2e\x35\x32\x30\x31\x2c\
+\x2d\x30\x2e\x38\x33\x36\x34\x20\x31\x2e\x38\x35\x36\x2c\x30\x6c\
+\x32\x2e\x39\x37\x36\x2c\x37\x2e\x34\x31\x31\x31\x63\x30\x2e\x31\
+\x34\x33\x31\x2c\x30\x2e\x33\x35\x36\x32\x20\x30\x2e\x34\x37\x37\
+\x34\x2c\x30\x2e\x35\x39\x39\x31\x20\x30\x2e\x38\x36\x30\x34\x2c\
+\x30\x2e\x36\x32\x35\x31\x6c\x37\x2e\x39\x36\x38\x2c\x30\x2e\x35\
+\x34\x30\x33\x63\x30\x2e\x38\x39\x39\x33\x2c\x30\x2e\x30\x36\x30\
+\x39\x20\x31\x2e\x32\x36\x35\x32\x2c\x31\x2e\x31\x38\x37\x31\x20\
+\x30\x2e\x35\x37\x33\x35\x2c\x31\x2e\x37\x36\x35\x31\x6c\x2d\x36\
+\x2e\x31\x32\x38\x37\x2c\x35\x2e\x31\x32\x30\x35\x63\x2d\x30\x2e\
+\x32\x39\x34\x36\x2c\x30\x2e\x32\x34\x36\x32\x20\x2d\x30\x2e\x34\
+\x32\x32\x33\x2c\x30\x2e\x36\x33\x39\x32\x20\x2d\x30\x2e\x33\x32\
+\x38\x36\x2c\x31\x2e\x30\x31\x31\x34\x6c\x31\x2e\x39\x34\x38\x34\
+\x2c\x37\x2e\x37\x34\x35\x63\x30\x2e\x32\x31\x39\x39\x2c\x30\x2e\
+\x38\x37\x34\x32\x20\x2d\x30\x2e\x37\x33\x38\x31\x2c\x31\x2e\x35\
+\x37\x30\x32\x20\x2d\x31\x2e\x35\x30\x31\x35\x2c\x31\x2e\x30\x39\
+\x30\x39\x6c\x2d\x36\x2e\x37\x36\x33\x38\x2c\x2d\x34\x2e\x32\x34\
+\x36\x34\x63\x2d\x30\x2e\x33\x32\x35\x31\x2c\x2d\x30\x2e\x32\x30\
+\x34\x31\x20\x2d\x30\x2e\x37\x33\x38\x33\x2c\x2d\x30\x2e\x32\x30\
+\x34\x31\x20\x2d\x31\x2e\x30\x36\x33\x34\x2c\x30\x6c\x2d\x36\x2e\
+\x37\x36\x33\x38\x2c\x34\x2e\x32\x34\x36\x34\x63\x2d\x30\x2e\x37\
+\x36\x33\x35\x2c\x30\x2e\x34\x37\x39\x33\x20\x2d\x31\x2e\x37\x32\
+\x31\x34\x2c\x2d\x30\x2e\x32\x31\x36\x37\x20\x2d\x31\x2e\x35\x30\
+\x31\x35\x2c\x2d\x31\x2e\x30\x39\x30\x39\x6c\x31\x2e\x39\x34\x38\
+\x34\x2c\x2d\x37\x2e\x37\x34\x35\x63\x30\x2e\x30\x39\x33\x36\x2c\
+\x2d\x30\x2e\x33\x37\x32\x32\x20\x2d\x30\x2e\x30\x33\x34\x31\x2c\
+\x2d\x30\x2e\x37\x36\x35\x32\x20\x2d\x30\x2e\x33\x32\x38\x36\x2c\
+\x2d\x31\x2e\x30\x31\x31\x34\x6c\x2d\x36\x2e\x31\x32\x38\x37\x2c\
+\x2d\x35\x2e\x31\x32\x30\x35\x63\x2d\x30\x2e\x36\x39\x31\x38\x2c\
+\x2d\x30\x2e\x35\x37\x38\x20\x2d\x30\x2e\x33\x32\x35\x38\x2c\x2d\
+\x31\x2e\x37\x30\x34\x32\x20\x30\x2e\x35\x37\x33\x35\x2c\x2d\x31\
+\x2e\x37\x36\x35\x31\x6c\x37\x2e\x39\x36\x38\x2c\x2d\x30\x2e\x35\
+\x34\x30\x33\x63\x30\x2e\x33\x38\x33\x2c\x2d\x30\x2e\x30\x32\x36\
+\x20\x30\x2e\x37\x31\x37\x33\x2c\x2d\x30\x2e\x32\x36\x38\x39\x20\
+\x30\x2e\x38\x36\x30\x33\x2c\x2d\x30\x2e\x36\x32\x35\x31\x6c\x32\
+\x2e\x39\x37\x36\x31\x2c\x2d\x37\x2e\x34\x31\x31\x31\x7a\x22\x20\
+\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\x43\x39\x34\x43\x22\x20\x69\
+\x64\x3d\x22\x73\x76\x67\x5f\x32\x22\x2f\x3e\x0a\x20\x20\x3c\x70\
+\x61\x74\x68\x20\x66\x69\x6c\x6c\x3d\x22\x23\x66\x66\x30\x30\x30\
+\x30\x22\x20\x64\x3d\x22\x6d\x35\x2c\x33\x39\x2e\x39\x39\x39\x39\
+\x34\x6c\x30\x2c\x30\x63\x30\x2c\x2d\x31\x38\x2e\x31\x36\x32\x36\
+\x38\x20\x31\x35\x2e\x36\x37\x30\x30\x35\x2c\x2d\x33\x32\x2e\x38\
+\x38\x36\x35\x34\x20\x33\x34\x2e\x39\x39\x39\x39\x37\x2c\x2d\x33\
+\x32\x2e\x38\x38\x36\x35\x34\x6c\x30\x2c\x30\x63\x39\x2e\x32\x38\
+\x32\x37\x2c\x30\x20\x31\x38\x2e\x31\x38\x35\x31\x37\x2c\x33\x2e\
+\x34\x36\x34\x38\x33\x20\x32\x34\x2e\x37\x34\x38\x37\x34\x2c\x39\
+\x2e\x36\x33\x32\x32\x38\x63\x36\x2e\x35\x36\x33\x38\x33\x2c\x36\
+\x2e\x31\x36\x37\x34\x35\x20\x31\x30\x2e\x32\x35\x31\x32\x39\x2c\
+\x31\x34\x2e\x35\x33\x32\x33\x32\x20\x31\x30\x2e\x32\x35\x31\x32\
+\x39\x2c\x32\x33\x2e\x32\x35\x34\x32\x36\x6c\x30\x2c\x30\x63\x30\
+\x2c\x31\x38\x2e\x31\x36\x32\x39\x20\x2d\x31\x35\x2e\x36\x36\x39\
+\x39\x34\x2c\x33\x32\x2e\x38\x38\x36\x36\x36\x20\x2d\x33\x35\x2e\
+\x30\x30\x30\x30\x33\x2c\x33\x32\x2e\x38\x38\x36\x36\x36\x6c\x30\
+\x2c\x30\x63\x2d\x31\x39\x2e\x33\x32\x39\x39\x32\x2c\x30\x20\x2d\
+\x33\x34\x2e\x39\x39\x39\x39\x37\x2c\x2d\x31\x34\x2e\x37\x32\x33\
+\x37\x36\x20\x2d\x33\x34\x2e\x39\x39\x39\x39\x37\x2c\x2d\x33\x32\
+\x2e\x38\x38\x36\x36\x36\x6c\x30\x2c\x30\x7a\x6d\x35\x36\x2e\x35\
+\x31\x37\x30\x33\x2c\x31\x34\x2e\x37\x31\x31\x38\x6c\x30\x2c\x30\
+\x63\x37\x2e\x37\x30\x35\x38\x34\x2c\x2d\x39\x2e\x39\x35\x30\x37\
+\x31\x20\x36\x2e\x35\x36\x30\x33\x39\x2c\x2d\x32\x33\x2e\x36\x39\
+\x30\x33\x36\x20\x2d\x32\x2e\x37\x30\x30\x35\x37\x2c\x2d\x33\x32\
+\x2e\x33\x39\x32\x63\x2d\x39\x2e\x32\x36\x30\x39\x36\x2c\x2d\x38\
+\x2e\x37\x30\x31\x37\x31\x20\x2d\x32\x33\x2e\x38\x38\x33\x35\x35\
+\x2c\x2d\x39\x2e\x37\x37\x38\x20\x2d\x33\x34\x2e\x34\x37\x33\x34\
+\x35\x2c\x2d\x32\x2e\x35\x33\x37\x33\x38\x6c\x33\x37\x2e\x31\x37\
+\x34\x30\x32\x2c\x33\x34\x2e\x39\x32\x39\x33\x38\x7a\x6d\x2d\x34\
+\x33\x2e\x30\x33\x33\x39\x33\x2c\x2d\x32\x39\x2e\x34\x32\x33\x33\
+\x63\x2d\x37\x2e\x37\x30\x35\x39\x2c\x39\x2e\x39\x35\x30\x36\x36\
+\x20\x2d\x36\x2e\x35\x36\x30\x34\x37\x2c\x32\x33\x2e\x36\x39\x30\
+\x33\x20\x32\x2e\x37\x30\x30\x34\x34\x2c\x33\x32\x2e\x33\x39\x31\
+\x38\x32\x63\x39\x2e\x32\x36\x30\x38\x38\x2c\x38\x2e\x37\x30\x31\
+\x37\x36\x20\x32\x33\x2e\x38\x38\x33\x34\x37\x2c\x39\x2e\x37\x37\
+\x38\x30\x35\x20\x33\x34\x2e\x34\x37\x33\x33\x37\x2c\x32\x2e\x35\
+\x33\x37\x35\x6c\x2d\x33\x37\x2e\x31\x37\x33\x38\x31\x2c\x2d\x33\
+\x34\x2e\x39\x32\x39\x33\x32\x6c\x30\x2c\x30\x7a\x22\x20\x69\x64\
+\x3d\x22\x73\x76\x67\x5f\x34\x22\x2f\x3e\x0a\x20\x3c\x2f\x67\x3e\
+\x0a\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x01\x6c\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x73\x68\x61\x72\x65\
+\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x34\x20\x31\x32\
+\x76\x38\x61\x32\x20\x32\x20\x30\x20\x30\x20\x30\x20\x32\x20\x32\
+\x68\x31\x32\x61\x32\x20\x32\x20\x30\x20\x30\x20\x30\x20\x32\x2d\
+\x32\x76\x2d\x38\x22\x3e\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x70\x6f\
+\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\
+\x36\x20\x36\x20\x31\x32\x20\x32\x20\x38\x20\x36\x22\x3e\x3c\x2f\
+\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\
+\x31\x3d\x22\x31\x32\x22\x20\x79\x31\x3d\x22\x32\x22\x20\x78\x32\
+\x3d\x22\x31\x32\x22\x20\x79\x32\x3d\x22\x31\x35\x22\x3e\x3c\x2f\
+\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x01\x33\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x63\x6f\x64\x65\x22\
+\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\
+\x73\x3d\x22\x31\x36\x20\x31\x38\x20\x32\x32\x20\x31\x32\x20\x31\
+\x36\x20\x36\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\
+\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\x73\
+\x3d\x22\x38\x20\x36\x20\x32\x20\x31\x32\x20\x38\x20\x31\x38\x22\
+\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\
+\x67\x3e\
+\x00\x00\x03\xa8\
+\x00\
+\x00\x0c\x99\x78\x9c\xdd\x56\x4b\x6f\xe3\x36\x10\xbe\xe7\x57\x08\
+\xca\xa5\x45\x23\x8a\xd4\xcb\xa2\x62\x79\x81\x36\x58\xb4\x87\x5e\
+\xba\xbb\xe8\x99\x21\x69\x5b\x1b\x89\x34\x28\x3a\xb6\xf7\xd7\xef\
+\x50\x0f\x5b\x76\x9c\xf4\x71\x28\xd0\x08\x36\xec\x79\x71\x66\xbe\
+\xf9\x38\xf6\xfc\xc3\xbe\xa9\xbd\x67\x69\xda\x4a\xab\xd2\x27\x08\
+\xfb\x9e\x54\x5c\x8b\x4a\xad\x4a\xff\xcb\xe7\x8f\x41\xee\x7b\xad\
+\x65\x4a\xb0\x5a\x2b\x59\xfa\x4a\xfb\x1f\x16\x37\xf3\xf6\x79\x75\
+\xe3\x79\x1e\x04\xab\xb6\x10\xbc\xf4\xd7\xd6\x6e\x8a\x30\xdc\x6c\
+\x4d\x8d\xb4\x59\x85\x82\x87\xb2\x96\x8d\x54\xb6\x0d\x09\x22\xa1\
+\x7f\x72\xe7\x27\x77\x6e\x24\xb3\xd5\xb3\xe4\xba\x69\xb4\x6a\xbb\
+\x48\xd5\xde\x4e\x9c\x8d\x58\x1e\xbd\x77\xbb\x1d\xda\xc5\x9d\x13\
+\xa1\x94\x86\x38\x0a\xa3\x28\x00\x8f\xa0\x3d\x28\xcb\xf6\xc1\x79\
+\x28\xd4\x78\x2d\x34\xc2\x18\x87\x60\x3b\x79\xfe\x3d\xaf\xa2\x05\
+\x54\x36\xf0\x3e\xba\x8f\x0a\xd4\xea\xad\xe1\x72\x09\x71\x12\x29\
+\x69\xc3\x87\xcf\x0f\x47\x63\x80\x91\xb0\x62\x72\x4c\xa5\x9e\x5a\
+\xce\x36\xf2\x2c\xeb\xa8\xec\x11\x60\x8d\x6c\x37\x8c\xcb\x36\x1c\
+\xf5\x5d\xfc\x28\x14\xd3\x79\x19\x4e\xbc\x1f\x30\xa5\x19\x16\xd9\
+\x12\xa7\x77\x5e\x84\x23\x1c\xe0\x24\xc0\xf4\xc7\x2e\x6a\x2c\xa4\
+\x10\x9a\xbb\x93\x4b\x5f\xee\x37\x30\x50\x34\x76\x57\x89\xd2\x87\
+\xef\x59\x27\x4c\x8e\x26\x9d\x82\xd7\xac\x05\x84\x96\x30\xa8\xb5\
+\x34\xde\xf0\x19\x00\x45\xfa\xa2\x5a\x6b\xf4\x93\x0c\xea\x4a\xc9\
+\xaf\xba\x82\x40\xa3\xb7\x4a\x5c\x9a\xa0\xec\x2b\x96\x5d\x25\xec\
+\xba\xf4\xa3\x89\xae\xf4\xf9\xd6\x18\xa0\xcd\x2f\xba\xd6\xa6\x33\
+\x2c\xab\xba\x76\xc4\x53\x7d\xc2\xe7\x4a\xee\x7e\xd6\xfb\xd2\xc7\
+\x1e\xf6\xa2\x04\x5e\x9d\x7a\x2d\xab\xd5\xda\xc2\x61\xbd\x38\x1e\
+\x9d\xf8\x0b\x10\xe7\x8d\xb4\x4c\x30\xcb\x9c\xa9\xef\x78\xd4\x90\
+\xa8\xf3\x00\x1f\x20\x52\xf1\xc7\xc3\xc7\x5e\x02\x99\xf3\xe2\x4f\
+\x6d\x9e\x06\x11\x1e\xe7\xc0\x1e\xf5\x16\xb2\xf8\x8b\xa3\x7a\x2e\
+\x78\x01\xa3\x6f\x98\x5d\x54\x0d\x5b\x49\xc7\x9a\x9f\x60\xd4\xf3\
+\xf0\x64\x38\x73\xb6\x87\x8d\x3c\x1d\xda\x1f\x6b\x64\xcf\xa1\xab\
+\x17\x49\xf0\xa6\x72\x41\xe1\x27\x0b\x50\xfc\xe6\x92\xf8\x5e\x78\
+\x71\x68\x65\x6b\xb9\xe8\x72\xf6\x5f\xc7\x2e\xc2\xa1\x8d\xa1\xc9\
+\x70\xd2\xe5\x3c\x1c\x41\xe8\x24\x21\x97\xed\x09\x1f\x27\x11\x3c\
+\xe4\x99\x1f\x49\xe4\x18\x24\xdc\x08\x06\xcf\x91\x92\xc3\xd4\x82\
+\x9a\x1d\xa4\x99\xf0\x69\xe2\xb2\xab\x94\xd0\xbb\xa0\x61\xfb\xaa\
+\xa9\xbe\x49\xc8\x81\x5f\x71\x39\x00\xfd\xf2\xf4\x15\x23\x4c\x9e\
+\xc4\xf9\xec\xd2\xca\x5d\x50\x84\x32\x1c\xc7\xf1\x8b\xd4\x7c\xdf\
+\x19\x93\x59\x7c\x25\xf2\x9b\xd6\x0d\x30\x85\xa2\x8c\xe6\xc9\x31\
+\x6d\xbb\xd6\xbb\x95\x71\x48\x2c\x59\xdd\x4a\xff\x84\xcc\x11\x82\
+\xfc\x95\x0a\x47\x2a\x12\x12\xbd\xe6\x32\xd0\x93\xd0\x59\x72\xe9\
+\xb1\x81\xf1\xb6\x6b\x06\x5e\xe3\xcd\xb8\x30\x6a\x58\x0d\xc0\x87\
+\x13\x7c\xab\x6d\x25\xa4\xd5\xb5\x34\x4c\x39\x0a\x91\xa3\x01\xea\
+\xbf\xa6\xd7\x8f\x5f\x25\xb7\xd7\x2c\x8f\xda\x08\x69\x8e\x19\xc8\
+\x99\x9a\xbb\x2b\x59\xfa\xb7\x59\xf7\x0c\x26\x57\xd1\x68\x58\x76\
+\xcf\xc8\x99\x0d\x6c\x8a\x01\x4b\x7b\xa8\x21\x8b\xbb\xc8\x85\xbb\
+\xc7\xf7\xfd\x5d\x2f\x6e\x71\xf7\xdc\x4f\xd7\x41\x11\xdd\x9f\xef\
+\x8d\xa2\x5b\x1b\xf7\x17\x7b\xa6\x80\x2b\x21\xcd\xa8\xed\x84\x1a\
+\x68\x65\x8b\x64\xd4\x09\x06\x28\x1a\xc3\x0e\xd3\x94\xc1\xd0\x5a\
+\x31\x76\x06\xf3\xfc\xdd\x4b\x50\x84\x73\x9a\xe5\xf4\x2e\x42\x69\
+\x4a\x71\x1a\x13\xef\x57\x2f\x22\x28\x4d\x28\x8d\xc8\x64\xf6\xae\
+\xa7\x3c\x7d\x49\x3e\xad\xa0\x56\xab\x61\x2f\x6e\xcd\x33\xb3\x5b\
+\x23\xdd\x78\xfe\xcf\x40\x10\x44\x31\xc5\x79\xf6\x26\x10\xf4\xfd\
+\x03\x41\x30\x8a\x28\xc5\xd9\xec\x2d\x20\x72\xf2\x5e\x81\x98\xa1\
+\x2c\x89\x93\x7c\x96\xdd\x65\x28\x89\x00\x87\x37\x61\x78\xb1\xb3\
+\xdf\x1f\x0c\x24\x41\x24\xa3\x78\x16\xbf\x09\xc4\xbf\xde\x10\x7f\
+\x15\x70\x79\x03\xd3\x53\x95\x8d\x07\x3f\x71\x18\xb8\x98\x02\x6b\
+\x73\x84\x63\xa8\x92\x7a\x6b\x8f\xa2\x24\x23\xb3\x6c\xfc\x2d\xf9\
+\x47\x50\xc3\x16\x48\xd3\x28\xff\x8f\x00\x77\x68\xcc\xdd\xff\xa7\
+\xc5\xcd\x77\xd2\xf3\xe7\xb2\
+\x00\x00\x03\x9b\
+\x3c\
+\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
+\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x76\x69\x65\x77\x42\
+\x6f\x78\x3d\x22\x30\x20\x30\x20\x38\x30\x20\x38\x30\x22\x20\x66\
+\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x78\x6d\x6c\x6e\x73\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x3e\x0a\x20\
+\x20\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\
+\x3d\x22\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x63\x6c\x69\x70\x2d\
+\x72\x75\x6c\x65\x3d\x22\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x64\
+\x3d\x22\x4d\x33\x33\x2e\x39\x32\x35\x36\x20\x39\x2e\x38\x38\x36\
+\x34\x37\x43\x33\x33\x2e\x31\x36\x34\x38\x20\x39\x2e\x38\x38\x36\
+\x34\x37\x20\x33\x32\x2e\x34\x34\x35\x34\x20\x31\x30\x2e\x32\x33\
+\x32\x39\x20\x33\x31\x2e\x39\x37\x31\x31\x20\x31\x30\x2e\x38\x32\
+\x37\x37\x4c\x32\x36\x2e\x36\x35\x30\x31\x20\x31\x37\x2e\x35\x48\
+\x31\x35\x43\x31\x33\x2e\x36\x31\x39\x33\x20\x31\x37\x2e\x35\x20\
+\x31\x32\x2e\x35\x20\x31\x38\x2e\x36\x31\x39\x33\x20\x31\x32\x2e\
+\x35\x20\x32\x30\x43\x31\x32\x2e\x35\x20\x32\x31\x2e\x33\x38\x30\
+\x37\x20\x31\x33\x2e\x36\x31\x39\x33\x20\x32\x32\x2e\x35\x20\x31\
+\x35\x20\x32\x32\x2e\x35\x48\x31\x36\x2e\x35\x56\x36\x34\x43\x31\
+\x36\x2e\x35\x20\x36\x37\x2e\x35\x38\x39\x39\x20\x31\x39\x2e\x34\
+\x31\x30\x31\x20\x37\x30\x2e\x35\x20\x32\x33\x20\x37\x30\x2e\x35\
+\x48\x35\x37\x43\x36\x30\x2e\x35\x38\x39\x38\x20\x37\x30\x2e\x35\
+\x20\x36\x33\x2e\x35\x20\x36\x37\x2e\x35\x38\x39\x39\x20\x36\x33\
+\x2e\x35\x20\x36\x34\x56\x32\x32\x2e\x35\x48\x36\x35\x43\x36\x36\
+\x2e\x33\x38\x30\x37\x20\x32\x32\x2e\x35\x20\x36\x37\x2e\x35\x20\
+\x32\x31\x2e\x33\x38\x30\x37\x20\x36\x37\x2e\x35\x20\x32\x30\x43\
+\x36\x37\x2e\x35\x20\x31\x38\x2e\x36\x31\x39\x33\x20\x36\x36\x2e\
+\x33\x38\x30\x37\x20\x31\x37\x2e\x35\x20\x36\x35\x20\x31\x37\x2e\
+\x35\x48\x35\x33\x2e\x33\x34\x39\x39\x4c\x34\x38\x2e\x30\x32\x39\
+\x20\x31\x30\x2e\x38\x32\x37\x38\x43\x34\x37\x2e\x35\x35\x34\x36\
+\x20\x31\x30\x2e\x32\x33\x32\x39\x20\x34\x36\x2e\x38\x33\x35\x32\
+\x20\x39\x2e\x38\x38\x36\x34\x37\x20\x34\x36\x2e\x30\x37\x34\x34\
+\x20\x39\x2e\x38\x38\x36\x34\x37\x48\x33\x33\x2e\x39\x32\x35\x36\
+\x5a\x4d\x33\x33\x20\x32\x37\x2e\x35\x43\x33\x34\x2e\x33\x38\x30\
+\x37\x20\x32\x37\x2e\x35\x20\x33\x35\x2e\x35\x20\x32\x38\x2e\x36\
+\x31\x39\x33\x20\x33\x35\x2e\x35\x20\x33\x30\x56\x35\x38\x43\x33\
+\x35\x2e\x35\x20\x35\x39\x2e\x33\x38\x30\x37\x20\x33\x34\x2e\x33\
+\x38\x30\x37\x20\x36\x30\x2e\x35\x20\x33\x33\x20\x36\x30\x2e\x35\
+\x43\x33\x31\x2e\x36\x31\x39\x33\x20\x36\x30\x2e\x35\x20\x33\x30\
+\x2e\x35\x20\x35\x39\x2e\x33\x38\x30\x37\x20\x33\x30\x2e\x35\x20\
+\x35\x38\x56\x33\x30\x43\x33\x30\x2e\x35\x20\x32\x38\x2e\x36\x31\
+\x39\x33\x20\x33\x31\x2e\x36\x31\x39\x33\x20\x32\x37\x2e\x35\x20\
+\x33\x33\x20\x32\x37\x2e\x35\x5a\x4d\x34\x39\x2e\x35\x20\x33\x30\
+\x43\x34\x39\x2e\x35\x20\x32\x38\x2e\x36\x31\x39\x33\x20\x34\x38\
+\x2e\x33\x38\x30\x37\x20\x32\x37\x2e\x35\x20\x34\x37\x20\x32\x37\
+\x2e\x35\x43\x34\x35\x2e\x36\x31\x39\x33\x20\x32\x37\x2e\x35\x20\
+\x34\x34\x2e\x35\x20\x32\x38\x2e\x36\x31\x39\x33\x20\x34\x34\x2e\
+\x35\x20\x33\x30\x56\x35\x38\x43\x34\x34\x2e\x35\x20\x35\x39\x2e\
+\x33\x38\x30\x37\x20\x34\x35\x2e\x36\x31\x39\x33\x20\x36\x30\x2e\
+\x35\x20\x34\x37\x20\x36\x30\x2e\x35\x43\x34\x38\x2e\x33\x38\x30\
+\x37\x20\x36\x30\x2e\x35\x20\x34\x39\x2e\x35\x20\x35\x39\x2e\x33\
+\x38\x30\x37\x20\x34\x39\x2e\x35\x20\x35\x38\x56\x33\x30\x5a\x4d\
+\x34\x36\x2e\x39\x35\x33\x36\x20\x31\x37\x2e\x34\x39\x38\x36\x4c\
+\x34\x34\x2e\x38\x37\x30\x34\x20\x31\x34\x2e\x38\x38\x36\x35\x48\
+\x33\x35\x2e\x31\x32\x39\x36\x4c\x33\x33\x2e\x30\x34\x36\x34\x20\
+\x31\x37\x2e\x34\x39\x38\x36\x48\x34\x36\x2e\x39\x35\x33\x36\x5a\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x43\x32\x43\x43\x44\x45\x22\
+\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x01\x88\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x73\x61\x76\x65\x22\
+\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x39\x20\x32\x31\
+\x48\x35\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x2d\x32\x2d\x32\
+\x56\x35\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x20\x32\x2d\x32\
+\x68\x31\x31\x6c\x35\x20\x35\x76\x31\x31\x61\x32\x20\x32\x20\x30\
+\x20\x30\x20\x31\x2d\x32\x20\x32\x7a\x22\x3e\x3c\x2f\x70\x61\x74\
+\x68\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\
+\x74\x73\x3d\x22\x31\x37\x20\x32\x31\x20\x31\x37\x20\x31\x33\x20\
+\x37\x20\x31\x33\x20\x37\x20\x32\x31\x22\x3e\x3c\x2f\x70\x6f\x6c\
+\x79\x6c\x69\x6e\x65\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\
+\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x20\x33\x20\x37\x20\x38\x20\
+\x31\x35\x20\x38\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\
+\x3e\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x01\x70\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x6c\x6f\x67\x2d\x69\
+\x6e\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x31\x35\x20\
+\x33\x68\x34\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x20\x32\x20\
+\x32\x76\x31\x34\x61\x32\x20\x32\x20\x30\x20\x30\x20\x31\x2d\x32\
+\x20\x32\x68\x2d\x34\x22\x3e\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x70\
+\x6f\x6c\x79\x6c\x69\x6e\x65\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\
+\x31\x30\x20\x31\x37\x20\x31\x35\x20\x31\x32\x20\x31\x30\x20\x37\
+\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x3e\x3c\x6c\x69\
+\x6e\x65\x20\x78\x31\x3d\x22\x31\x35\x22\x20\x79\x31\x3d\x22\x31\
+\x32\x22\x20\x78\x32\x3d\x22\x33\x22\x20\x79\x32\x3d\x22\x31\x32\
+\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x05\xba\
+\x3c\
+\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
+\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x76\x69\x65\x77\x42\
+\x6f\x78\x3d\x22\x30\x20\x30\x20\x38\x30\x20\x38\x30\x22\x20\x66\
+\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x78\x6d\x6c\x6e\x73\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x3e\x0a\x20\
+\x20\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x38\x2e\x31\x34\
+\x34\x31\x20\x31\x32\x2e\x36\x32\x31\x37\x43\x33\x38\x2e\x38\x31\
+\x35\x39\x20\x31\x30\x2e\x39\x34\x38\x38\x20\x34\x31\x2e\x31\x38\
+\x34\x31\x20\x31\x30\x2e\x39\x34\x38\x38\x20\x34\x31\x2e\x38\x35\
+\x35\x39\x20\x31\x32\x2e\x36\x32\x31\x37\x4c\x34\x37\x2e\x38\x30\
+\x38\x31\x20\x32\x37\x2e\x34\x34\x33\x39\x43\x34\x38\x2e\x30\x39\
+\x34\x32\x20\x32\x38\x2e\x31\x35\x36\x33\x20\x34\x38\x2e\x37\x36\
+\x32\x38\x20\x32\x38\x2e\x36\x34\x32\x20\x34\x39\x2e\x35\x32\x38\
+\x38\x20\x32\x38\x2e\x36\x39\x34\x4c\x36\x35\x2e\x34\x36\x34\x38\
+\x20\x32\x39\x2e\x37\x37\x34\x35\x43\x36\x37\x2e\x32\x36\x33\x35\
+\x20\x32\x39\x2e\x38\x39\x36\x35\x20\x36\x37\x2e\x39\x39\x35\x33\
+\x20\x33\x32\x2e\x31\x34\x38\x39\x20\x36\x36\x2e\x36\x31\x31\x38\
+\x20\x33\x33\x2e\x33\x30\x34\x37\x4c\x35\x34\x2e\x33\x35\x34\x34\
+\x20\x34\x33\x2e\x35\x34\x35\x39\x43\x35\x33\x2e\x37\x36\x35\x33\
+\x20\x34\x34\x2e\x30\x33\x38\x31\x20\x35\x33\x2e\x35\x30\x39\x39\
+\x20\x34\x34\x2e\x38\x32\x34\x31\x20\x35\x33\x2e\x36\x39\x37\x32\
+\x20\x34\x35\x2e\x35\x36\x38\x36\x4c\x35\x37\x2e\x35\x39\x34\x31\
+\x20\x36\x31\x2e\x30\x35\x38\x36\x43\x35\x38\x2e\x30\x33\x33\x39\
+\x20\x36\x32\x2e\x38\x30\x36\x39\x20\x35\x36\x2e\x31\x31\x37\x39\
+\x20\x36\x34\x2e\x31\x39\x39\x20\x35\x34\x2e\x35\x39\x31\x31\x20\
+\x36\x33\x2e\x32\x34\x30\x34\x4c\x34\x31\x2e\x30\x36\x33\x34\x20\
+\x35\x34\x2e\x37\x34\x37\x36\x43\x34\x30\x2e\x34\x31\x33\x32\x20\
+\x35\x34\x2e\x33\x33\x39\x34\x20\x33\x39\x2e\x35\x38\x36\x38\x20\
+\x35\x34\x2e\x33\x33\x39\x34\x20\x33\x38\x2e\x39\x33\x36\x36\x20\
+\x35\x34\x2e\x37\x34\x37\x36\x4c\x32\x35\x2e\x34\x30\x38\x39\x20\
+\x36\x33\x2e\x32\x34\x30\x34\x43\x32\x33\x2e\x38\x38\x32\x31\x20\
+\x36\x34\x2e\x31\x39\x39\x20\x32\x31\x2e\x39\x36\x36\x31\x20\x36\
+\x32\x2e\x38\x30\x36\x39\x20\x32\x32\x2e\x34\x30\x35\x39\x20\x36\
+\x31\x2e\x30\x35\x38\x36\x4c\x32\x36\x2e\x33\x30\x32\x38\x20\x34\
+\x35\x2e\x35\x36\x38\x36\x43\x32\x36\x2e\x34\x39\x30\x31\x20\x34\
+\x34\x2e\x38\x32\x34\x31\x20\x32\x36\x2e\x32\x33\x34\x37\x20\x34\
+\x34\x2e\x30\x33\x38\x31\x20\x32\x35\x2e\x36\x34\x35\x36\x20\x34\
+\x33\x2e\x35\x34\x35\x39\x4c\x31\x33\x2e\x33\x38\x38\x32\x20\x33\
+\x33\x2e\x33\x30\x34\x37\x43\x31\x32\x2e\x30\x30\x34\x37\x20\x33\
+\x32\x2e\x31\x34\x38\x39\x20\x31\x32\x2e\x37\x33\x36\x35\x20\x32\
+\x39\x2e\x38\x39\x36\x35\x20\x31\x34\x2e\x35\x33\x35\x32\x20\x32\
+\x39\x2e\x37\x37\x34\x35\x4c\x33\x30\x2e\x34\x37\x31\x32\x20\x32\
+\x38\x2e\x36\x39\x34\x43\x33\x31\x2e\x32\x33\x37\x32\x20\x32\x38\
+\x2e\x36\x34\x32\x20\x33\x31\x2e\x39\x30\x35\x38\x20\x32\x38\x2e\
+\x31\x35\x36\x33\x20\x33\x32\x2e\x31\x39\x31\x39\x20\x32\x37\x2e\
+\x34\x34\x33\x39\x4c\x33\x38\x2e\x31\x34\x34\x31\x20\x31\x32\x2e\
+\x36\x32\x31\x37\x5a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\
+\x39\x39\x34\x41\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x20\x64\x3d\x22\x4d\x33\x39\x2e\x30\x35\x31\x35\x20\x32\x36\x2e\
+\x33\x31\x30\x38\x43\x33\x39\x2e\x33\x38\x37\x34\x20\x32\x35\x2e\
+\x34\x37\x34\x34\x20\x34\x30\x2e\x35\x37\x31\x36\x20\x32\x35\x2e\
+\x34\x37\x34\x34\x20\x34\x30\x2e\x39\x30\x37\x35\x20\x32\x36\x2e\
+\x33\x31\x30\x38\x4c\x34\x33\x2e\x38\x38\x33\x35\x20\x33\x33\x2e\
+\x37\x32\x31\x39\x43\x34\x34\x2e\x30\x32\x36\x36\x20\x33\x34\x2e\
+\x30\x37\x38\x31\x20\x34\x34\x2e\x33\x36\x30\x39\x20\x33\x34\x2e\
+\x33\x32\x31\x20\x34\x34\x2e\x37\x34\x33\x39\x20\x33\x34\x2e\x33\
+\x34\x37\x4c\x35\x32\x2e\x37\x31\x31\x39\x20\x33\x34\x2e\x38\x38\
+\x37\x33\x43\x35\x33\x2e\x36\x31\x31\x32\x20\x33\x34\x2e\x39\x34\
+\x38\x32\x20\x35\x33\x2e\x39\x37\x37\x31\x20\x33\x36\x2e\x30\x37\
+\x34\x34\x20\x35\x33\x2e\x32\x38\x35\x34\x20\x33\x36\x2e\x36\x35\
+\x32\x34\x4c\x34\x37\x2e\x31\x35\x36\x37\x20\x34\x31\x2e\x37\x37\
+\x32\x39\x43\x34\x36\x2e\x38\x36\x32\x31\x20\x34\x32\x2e\x30\x31\
+\x39\x31\x20\x34\x36\x2e\x37\x33\x34\x34\x20\x34\x32\x2e\x34\x31\
+\x32\x31\x20\x34\x36\x2e\x38\x32\x38\x31\x20\x34\x32\x2e\x37\x38\
+\x34\x33\x4c\x34\x38\x2e\x37\x37\x36\x35\x20\x35\x30\x2e\x35\x32\
+\x39\x33\x43\x34\x38\x2e\x39\x39\x36\x34\x20\x35\x31\x2e\x34\x30\
+\x33\x35\x20\x34\x38\x2e\x30\x33\x38\x34\x20\x35\x32\x2e\x30\x39\
+\x39\x35\x20\x34\x37\x2e\x32\x37\x35\x20\x35\x31\x2e\x36\x32\x30\
+\x32\x4c\x34\x30\x2e\x35\x31\x31\x32\x20\x34\x37\x2e\x33\x37\x33\
+\x38\x43\x34\x30\x2e\x31\x38\x36\x31\x20\x34\x37\x2e\x31\x36\x39\
+\x37\x20\x33\x39\x2e\x37\x37\x32\x39\x20\x34\x37\x2e\x31\x36\x39\
+\x37\x20\x33\x39\x2e\x34\x34\x37\x38\x20\x34\x37\x2e\x33\x37\x33\
+\x38\x4c\x33\x32\x2e\x36\x38\x34\x20\x35\x31\x2e\x36\x32\x30\x32\
+\x43\x33\x31\x2e\x39\x32\x30\x35\x20\x35\x32\x2e\x30\x39\x39\x35\
+\x20\x33\x30\x2e\x39\x36\x32\x36\x20\x35\x31\x2e\x34\x30\x33\x35\
+\x20\x33\x31\x2e\x31\x38\x32\x35\x20\x35\x30\x2e\x35\x32\x39\x33\
+\x4c\x33\x33\x2e\x31\x33\x30\x39\x20\x34\x32\x2e\x37\x38\x34\x33\
+\x43\x33\x33\x2e\x32\x32\x34\x35\x20\x34\x32\x2e\x34\x31\x32\x31\
+\x20\x33\x33\x2e\x30\x39\x36\x38\x20\x34\x32\x2e\x30\x31\x39\x31\
+\x20\x33\x32\x2e\x38\x30\x32\x33\x20\x34\x31\x2e\x37\x37\x32\x39\
+\x4c\x32\x36\x2e\x36\x37\x33\x36\x20\x33\x36\x2e\x36\x35\x32\x34\
+\x43\x32\x35\x2e\x39\x38\x31\x38\x20\x33\x36\x2e\x30\x37\x34\x34\
+\x20\x32\x36\x2e\x33\x34\x37\x38\x20\x33\x34\x2e\x39\x34\x38\x32\
+\x20\x32\x37\x2e\x32\x34\x37\x31\x20\x33\x34\x2e\x38\x38\x37\x33\
+\x4c\x33\x35\x2e\x32\x31\x35\x31\x20\x33\x34\x2e\x33\x34\x37\x43\
+\x33\x35\x2e\x35\x39\x38\x31\x20\x33\x34\x2e\x33\x32\x31\x20\x33\
+\x35\x2e\x39\x33\x32\x34\x20\x33\x34\x2e\x30\x37\x38\x31\x20\x33\
+\x36\x2e\x30\x37\x35\x34\x20\x33\x33\x2e\x37\x32\x31\x39\x4c\x33\
+\x39\x2e\x30\x35\x31\x35\x20\x32\x36\x2e\x33\x31\x30\x38\x5a\x22\
+\x20\x66\x69\x6c\x6c\x3d\x22\x23\x46\x32\x43\x39\x34\x43\x22\x20\
+\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x04\xc2\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x75\x74\x66\
+\x2d\x38\x22\x3f\x3e\x3c\x21\x2d\x2d\x20\x55\x70\x6c\x6f\x61\x64\
+\x65\x64\x20\x74\x6f\x3a\x20\x53\x56\x47\x20\x52\x65\x70\x6f\x2c\
+\x20\x77\x77\x77\x2e\x73\x76\x67\x72\x65\x70\x6f\x2e\x63\x6f\x6d\
+\x2c\x20\x47\x65\x6e\x65\x72\x61\x74\x6f\x72\x3a\x20\x53\x56\x47\
+\x20\x52\x65\x70\x6f\x20\x4d\x69\x78\x65\x72\x20\x54\x6f\x6f\x6c\
+\x73\x20\x2d\x2d\x3e\x0a\x3c\x73\x76\x67\x20\x77\x69\x64\x74\x68\
+\x3d\x22\x38\x30\x30\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\
+\x22\x38\x30\x30\x70\x78\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\
+\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\x22\x20\x66\x69\x6c\x6c\
+\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\
+\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\
+\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x3e\x0a\x3c\x70\x61\x74\
+\x68\x20\x64\x3d\x22\x4d\x32\x30\x20\x39\x2e\x35\x30\x31\x39\x35\
+\x56\x38\x2e\x37\x34\x39\x38\x35\x43\x32\x30\x20\x37\x2e\x35\x30\
+\x37\x32\x31\x20\x31\x38\x2e\x39\x39\x32\x36\x20\x36\x2e\x34\x39\
+\x39\x38\x35\x20\x31\x37\x2e\x37\x35\x20\x36\x2e\x34\x39\x39\x38\
+\x35\x48\x31\x32\x2e\x30\x32\x34\x37\x4c\x39\x2e\x36\x34\x33\x36\
+\x38\x20\x34\x2e\x35\x31\x39\x39\x35\x43\x39\x2e\x32\x33\x39\x35\
+\x39\x20\x34\x2e\x31\x38\x33\x39\x33\x20\x38\x2e\x37\x33\x30\x36\
+\x33\x20\x33\x2e\x39\x39\x39\x39\x37\x20\x38\x2e\x32\x30\x35\x30\
+\x39\x20\x33\x2e\x39\x39\x39\x39\x37\x48\x34\x2e\x32\x34\x39\x35\
+\x37\x43\x33\x2e\x30\x30\x37\x32\x34\x20\x33\x2e\x39\x39\x39\x39\
+\x37\x20\x32\x20\x35\x2e\x30\x30\x36\x38\x36\x20\x31\x2e\x39\x39\
+\x39\x35\x37\x20\x36\x2e\x32\x34\x39\x31\x39\x4c\x31\x2e\x39\x39\
+\x35\x36\x31\x20\x31\x37\x2e\x37\x34\x39\x32\x43\x31\x2e\x39\x39\
+\x35\x31\x38\x20\x31\x38\x2e\x39\x39\x32\x31\x20\x33\x2e\x30\x30\
+\x32\x36\x36\x20\x32\x30\x20\x34\x2e\x32\x34\x35\x36\x31\x20\x32\
+\x30\x48\x34\x2e\x32\x37\x31\x39\x36\x43\x34\x2e\x32\x37\x36\x30\
+\x37\x20\x32\x30\x20\x34\x2e\x32\x38\x30\x31\x39\x20\x32\x30\x20\
+\x34\x2e\x32\x38\x34\x33\x31\x20\x32\x30\x48\x31\x38\x2e\x34\x36\
+\x39\x33\x43\x31\x39\x2e\x32\x37\x32\x33\x20\x32\x30\x20\x31\x39\
+\x2e\x39\x37\x32\x33\x20\x31\x39\x2e\x34\x35\x33\x35\x20\x32\x30\
+\x2e\x31\x36\x37\x20\x31\x38\x2e\x36\x37\x34\x35\x4c\x32\x31\x2e\
+\x39\x31\x36\x39\x20\x31\x31\x2e\x36\x37\x36\x35\x43\x32\x32\x2e\
+\x31\x39\x33\x31\x20\x31\x30\x2e\x35\x37\x31\x39\x20\x32\x31\x2e\
+\x33\x35\x37\x37\x20\x39\x2e\x35\x30\x31\x39\x35\x20\x32\x30\x2e\
+\x32\x31\x39\x32\x20\x39\x2e\x35\x30\x31\x39\x35\x48\x32\x30\x5a\
+\x4d\x34\x2e\x32\x34\x39\x35\x37\x20\x35\x2e\x34\x39\x39\x39\x37\
+\x48\x38\x2e\x32\x30\x35\x30\x39\x43\x38\x2e\x33\x38\x30\x32\x37\
+\x20\x35\x2e\x34\x39\x39\x39\x37\x20\x38\x2e\x35\x34\x39\x39\x33\
+\x20\x35\x2e\x35\x36\x31\x32\x39\x20\x38\x2e\x36\x38\x34\x36\x32\
+\x20\x35\x2e\x36\x37\x33\x33\x4c\x31\x31\x2e\x32\x37\x34\x31\x20\
+\x37\x2e\x38\x32\x36\x35\x32\x43\x31\x31\x2e\x34\x30\x38\x38\x20\
+\x37\x2e\x39\x33\x38\x35\x32\x20\x31\x31\x2e\x35\x37\x38\x34\x20\
+\x37\x2e\x39\x39\x39\x38\x35\x20\x31\x31\x2e\x37\x35\x33\x36\x20\
+\x37\x2e\x39\x39\x39\x38\x35\x48\x31\x37\x2e\x37\x35\x43\x31\x38\
+\x2e\x31\x36\x34\x32\x20\x37\x2e\x39\x39\x39\x38\x35\x20\x31\x38\
+\x2e\x35\x20\x38\x2e\x33\x33\x35\x36\x33\x20\x31\x38\x2e\x35\x20\
+\x38\x2e\x37\x34\x39\x38\x35\x56\x39\x2e\x35\x30\x31\x39\x35\x48\
+\x36\x2e\x34\x32\x33\x38\x35\x43\x35\x2e\x33\x39\x31\x33\x36\x20\
+\x39\x2e\x35\x30\x31\x39\x35\x20\x34\x2e\x34\x39\x31\x33\x37\x20\
+\x31\x30\x2e\x32\x30\x34\x37\x20\x34\x2e\x32\x34\x31\x20\x31\x31\
+\x2e\x32\x30\x36\x34\x4c\x33\x2e\x34\x39\x36\x38\x34\x20\x31\x34\
+\x2e\x31\x38\x33\x37\x4c\x33\x2e\x34\x39\x39\x35\x37\x20\x36\x2e\
+\x32\x34\x39\x37\x31\x43\x33\x2e\x34\x39\x39\x37\x31\x20\x35\x2e\
+\x38\x33\x35\x36\x20\x33\x2e\x38\x33\x35\x34\x36\x20\x35\x2e\x34\
+\x39\x39\x39\x37\x20\x34\x2e\x32\x34\x39\x35\x37\x20\x35\x2e\x34\
+\x39\x39\x39\x37\x5a\x4d\x35\x2e\x36\x39\x36\x32\x33\x20\x31\x31\
+\x2e\x35\x37\x30\x31\x43\x35\x2e\x37\x37\x39\x36\x39\x20\x31\x31\
+\x2e\x32\x33\x36\x32\x20\x36\x2e\x30\x37\x39\x36\x39\x20\x31\x31\
+\x2e\x30\x30\x32\x20\x36\x2e\x34\x32\x33\x38\x35\x20\x31\x31\x2e\
+\x30\x30\x32\x48\x32\x30\x2e\x32\x31\x39\x32\x43\x32\x30\x2e\x33\
+\x38\x31\x39\x20\x31\x31\x2e\x30\x30\x32\x20\x32\x30\x2e\x35\x30\
+\x31\x32\x20\x31\x31\x2e\x31\x35\x34\x38\x20\x32\x30\x2e\x34\x36\
+\x31\x37\x20\x31\x31\x2e\x33\x31\x32\x36\x4c\x31\x38\x2e\x37\x31\
+\x31\x39\x20\x31\x38\x2e\x33\x31\x30\x37\x43\x31\x38\x2e\x36\x38\
+\x34\x20\x31\x38\x2e\x34\x32\x31\x39\x20\x31\x38\x2e\x35\x38\x34\
+\x20\x31\x38\x2e\x35\x20\x31\x38\x2e\x34\x36\x39\x33\x20\x31\x38\
+\x2e\x35\x48\x34\x2e\x32\x38\x34\x33\x31\x43\x34\x2e\x31\x32\x31\
+\x36\x37\x20\x31\x38\x2e\x35\x20\x34\x2e\x30\x30\x32\x33\x33\x20\
+\x31\x38\x2e\x33\x34\x37\x32\x20\x34\x2e\x30\x34\x31\x37\x37\x20\
+\x31\x38\x2e\x31\x38\x39\x34\x4c\x35\x2e\x36\x39\x36\x32\x33\x20\
+\x31\x31\x2e\x35\x37\x30\x31\x5a\x22\x20\x66\x69\x6c\x6c\x3d\x22\
+\x23\x32\x31\x32\x31\x32\x31\x22\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\
+\x3e\
+\x00\x00\x09\x8f\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
+\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\
+\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\
+\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\
+\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\
+\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\
+\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\
+\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\
+\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\
+\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
+\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
+\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
+\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\
+\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
+\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\
+\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
+\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
+\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\
+\x6f\x64\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\
+\x70\x6f\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\
+\x2e\x6e\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\
+\x69\x2d\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\
+\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\x22\x68\x74\x74\x70\
+\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\
+\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\x63\x65\x73\x2f\x69\
+\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\
+\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x72\x63\x31\x20\x28\x30\x39\x39\x36\x30\x64\x36\x66\x30\x35\
+\x2c\x20\x32\x30\x32\x30\x2d\x30\x34\x2d\x30\x39\x29\x22\x0a\x20\
+\x20\x20\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\
+\x6d\x65\x3d\x22\x63\x6f\x6c\x6c\x61\x70\x73\x65\x2e\x73\x76\x67\
+\x22\x0a\x20\x20\x20\x69\x64\x3d\x22\x73\x76\x67\x36\x22\x0a\x20\
+\x20\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\
+\x20\x20\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\x65\
+\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x63\x6f\x64\x65\x22\x0a\
+\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\
+\x69\x6e\x3d\x22\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\
+\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3d\x22\x72\x6f\
+\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\
+\x69\x64\x74\x68\x3d\x22\x32\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\
+\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\x6f\x72\
+\x22\x0a\x20\x20\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\
+\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\
+\x20\x32\x34\x20\x32\x34\x22\x0a\x20\x20\x20\x68\x65\x69\x67\x68\
+\x74\x3d\x22\x32\x34\x22\x0a\x20\x20\x20\x77\x69\x64\x74\x68\x3d\
+\x22\x32\x34\x22\x3e\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\x61\x74\
+\x61\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\x61\x64\
+\x61\x74\x61\x31\x32\x22\x3e\x0a\x20\x20\x20\x20\x3c\x72\x64\x66\
+\x3a\x52\x44\x46\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x63\x63\x3a\
+\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\x72\x64\
+\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x0a\x20\x20\x20\x20\
+\x20\x20\x20\x20\x3c\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x69\
+\x6d\x61\x67\x65\x2f\x73\x76\x67\x2b\x78\x6d\x6c\x3c\x2f\x64\x63\
+\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x0a\x20\x20\x20\x20\x20\x20\x20\
+\x20\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\x20\x20\
+\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\x72\x63\
+\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\x2e\x6f\
+\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\x2f\x53\
+\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x0a\x20\x20\
+\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\
+\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x20\x20\
+\x20\x20\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x0a\x20\x20\x20\
+\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\x3c\x2f\
+\x6d\x65\x74\x61\x64\x61\x74\x61\x3e\x0a\x20\x20\x3c\x64\x65\x66\
+\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\x73\x31\
+\x30\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x73\x6f\x64\x69\x70\x6f\x64\
+\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\x20\x20\
+\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\x65\x6e\
+\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\x67\x36\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
+\x64\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\x22\x30\
+\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\
+\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x31\x38\x35\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
+\x64\x6f\x77\x2d\x78\x3d\x22\x31\x33\x38\x37\x22\x0a\x20\x20\x20\
+\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\x22\x31\
+\x32\x2e\x31\x38\x37\x33\x38\x32\x22\x0a\x20\x20\x20\x20\x20\x69\
+\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\x3d\x22\x31\x32\x2e\x34\
+\x37\x33\x33\x38\x37\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\
+\x63\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x32\x39\x2e\x36\x39\
+\x38\x34\x38\x35\x22\x0a\x20\x20\x20\x20\x20\x73\x68\x6f\x77\x67\
+\x72\x69\x64\x3d\x22\x66\x61\x6c\x73\x65\x22\x0a\x20\x20\x20\x20\
+\x20\x69\x64\x3d\x22\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x38\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
+\x69\x6e\x64\x6f\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x31\x31\
+\x32\x38\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\
+\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\
+\x31\x39\x37\x34\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\
+\x61\x70\x65\x3a\x70\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\
+\x32\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\
+\x3a\x70\x61\x67\x65\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x22\
+\x0a\x20\x20\x20\x20\x20\x67\x75\x69\x64\x65\x74\x6f\x6c\x65\x72\
+\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x67\
+\x72\x69\x64\x74\x6f\x6c\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\
+\x22\x0a\x20\x20\x20\x20\x20\x6f\x62\x6a\x65\x63\x74\x74\x6f\x6c\
+\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\
+\x20\x62\x6f\x72\x64\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\
+\x31\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\
+\x6c\x6f\x72\x3d\x22\x23\x36\x36\x36\x36\x36\x36\x22\x0a\x20\x20\
+\x20\x20\x20\x70\x61\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\
+\x66\x66\x66\x66\x66\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\
+\x68\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\
+\x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x23\
+\x30\x30\x30\x30\x30\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\
+\x64\x74\x68\x3a\x31\x2e\x38\x39\x34\x34\x33\x3b\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\
+\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\
+\x3a\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\
+\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\x6b\
+\x65\x2d\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\x65\
+\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\
+\x31\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\x22\x4d\x20\x32\x2e\x38\
+\x39\x35\x37\x37\x30\x36\x2c\x33\x2e\x37\x33\x37\x35\x36\x34\x34\
+\x20\x48\x20\x32\x30\x2e\x32\x33\x36\x37\x32\x33\x22\x0a\x20\x20\
+\x20\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x38\x35\x37\x22\x0a\
+\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\
+\x6e\x6e\x65\x63\x74\x6f\x72\x2d\x63\x75\x72\x76\x61\x74\x75\x72\
+\x65\x3d\x22\x30\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\
+\x6c\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\x23\x30\
+\x30\x30\x30\x30\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\
+\x74\x68\x3a\x31\x2e\x38\x39\x34\x34\x33\x3b\x73\x74\x72\x6f\x6b\
+\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\x64\x3b\
+\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3a\
+\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\x69\x74\
+\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\x6b\x65\
+\x2d\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\x65\x3b\
+\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\x3a\x31\
+\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\x22\x4d\x20\x32\x2e\x38\x39\
+\x35\x37\x37\x30\x36\x2c\x31\x31\x2e\x34\x33\x37\x31\x37\x32\x20\
+\x48\x20\x32\x30\x2e\x32\x33\x36\x37\x32\x33\x22\x0a\x20\x20\x20\
+\x20\x20\x69\x64\x3d\x22\x70\x61\x74\x68\x38\x35\x39\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\x6e\
+\x6e\x65\x63\x74\x6f\x72\x2d\x63\x75\x72\x76\x61\x74\x75\x72\x65\
+\x3d\x22\x30\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x0a\
+\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x6f\
+\x6e\x6e\x65\x63\x74\x6f\x72\x2d\x63\x75\x72\x76\x61\x74\x75\x72\
+\x65\x3d\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x70\
+\x61\x74\x68\x38\x35\x35\x22\x0a\x20\x20\x20\x20\x20\x64\x3d\x22\
+\x4d\x20\x32\x2e\x38\x39\x35\x37\x37\x30\x36\x2c\x37\x2e\x35\x38\
+\x37\x33\x36\x38\x31\x20\x48\x20\x32\x30\x2e\x32\x33\x36\x37\x32\
+\x33\x22\x0a\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\
+\x69\x6c\x6c\x3a\x6e\x6f\x6e\x65\x3b\x73\x74\x72\x6f\x6b\x65\x3a\
+\x23\x30\x30\x30\x30\x30\x30\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x77\
+\x69\x64\x74\x68\x3a\x31\x2e\x38\x39\x34\x34\x33\x3b\x73\x74\x72\
+\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3a\x72\x6f\x75\x6e\
+\x64\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\
+\x6e\x3a\x6d\x69\x74\x65\x72\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6d\
+\x69\x74\x65\x72\x6c\x69\x6d\x69\x74\x3a\x34\x3b\x73\x74\x72\x6f\
+\x6b\x65\x2d\x64\x61\x73\x68\x61\x72\x72\x61\x79\x3a\x6e\x6f\x6e\
+\x65\x3b\x73\x74\x72\x6f\x6b\x65\x2d\x6f\x70\x61\x63\x69\x74\x79\
+\x3a\x31\x22\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
+\x00\x00\x07\x9d\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
+\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\
+\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\
+\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\
+\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\
+\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\
+\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\
+\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\
+\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\
+\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
+\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
+\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
+\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\
+\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
+\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\
+\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
+\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
+\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\
+\x6f\x64\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\
+\x70\x6f\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\
+\x2e\x6e\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\
+\x69\x2d\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\
+\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\x22\x68\x74\x74\x70\
+\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\
+\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\x63\x65\x73\x2f\x69\
+\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\
+\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x72\x63\x31\x20\x28\x30\x39\x39\x36\x30\x64\x36\x66\x30\x35\
+\x2c\x20\x32\x30\x32\x30\x2d\x30\x34\x2d\x30\x39\x29\x22\x0a\x20\
+\x20\x20\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\
+\x6d\x65\x3d\x22\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\
+\x6e\x2d\x72\x65\x64\x2e\x73\x76\x67\x22\x0a\x20\x20\x20\x69\x64\
+\x3d\x22\x73\x76\x67\x38\x22\x0a\x20\x20\x20\x76\x65\x72\x73\x69\
+\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\x20\x20\x20\x63\x6c\x61\x73\
+\x73\x3d\x22\x66\x65\x61\x74\x68\x65\x72\x20\x66\x65\x61\x74\x68\
+\x65\x72\x2d\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\x6e\
+\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\
+\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3d\x22\
+\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\
+\x2d\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x0a\x20\x20\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x0a\x20\x20\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\
+\x65\x22\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
+\x20\x30\x20\x32\x34\x20\x32\x34\x22\x0a\x20\x20\x20\x68\x65\x69\
+\x67\x68\x74\x3d\x22\x32\x34\x22\x0a\x20\x20\x20\x77\x69\x64\x74\
+\x68\x3d\x22\x32\x34\x22\x3e\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\
+\x61\x74\x61\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\
+\x61\x64\x61\x74\x61\x31\x34\x22\x3e\x0a\x20\x20\x20\x20\x3c\x72\
+\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x63\
+\x63\x3a\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
+\x72\x64\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x0a\x20\x20\
+\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\
+\x3e\x69\x6d\x61\x67\x65\x2f\x73\x76\x67\x2b\x78\x6d\x6c\x3c\x2f\
+\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x0a\x20\x20\x20\x20\x20\
+\x20\x20\x20\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\
+\x20\x20\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\
+\x72\x63\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\
+\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\
+\x2f\x53\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x0a\
+\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x74\x69\x74\x6c\
+\x65\x3e\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\
+\x20\x20\x20\x20\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x0a\x20\
+\x20\x20\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\
+\x3c\x2f\x6d\x65\x74\x61\x64\x61\x74\x61\x3e\x0a\x20\x20\x3c\x64\
+\x65\x66\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\
+\x73\x31\x32\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x73\x6f\x64\x69\x70\
+\x6f\x64\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\
+\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\
+\x65\x6e\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\x67\x38\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
+\x69\x6e\x64\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\
+\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\
+\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x32\x38\x31\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
+\x69\x6e\x64\x6f\x77\x2d\x78\x3d\x22\x31\x34\x33\x33\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\
+\x22\x31\x32\x2e\x34\x30\x39\x37\x39\x37\x22\x0a\x20\x20\x20\x20\
+\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\x3d\x22\x31\x32\
+\x2e\x38\x32\x38\x32\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\
+\x6b\x73\x63\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x32\x31\x22\
+\x0a\x20\x20\x20\x20\x20\x73\x68\x6f\x77\x67\x72\x69\x64\x3d\x22\
+\x66\x61\x6c\x73\x65\x22\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\
+\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x31\x30\x22\x0a\x20\x20\x20\
+\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\
+\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x39\x35\x39\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
+\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\x31\x35\x34\x32\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\
+\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\x32\x22\x0a\x20\x20\
+\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\x65\
+\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x22\x0a\x20\x20\x20\x20\
+\x20\x67\x75\x69\x64\x65\x74\x6f\x6c\x65\x72\x61\x6e\x63\x65\x3d\
+\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x67\x72\x69\x64\x74\x6f\
+\x6c\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\
+\x20\x20\x6f\x62\x6a\x65\x63\x74\x74\x6f\x6c\x65\x72\x61\x6e\x63\
+\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\
+\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x31\x22\x0a\x20\x20\
+\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\x6c\x6f\x72\x3d\x22\
+\x23\x36\x36\x36\x36\x36\x36\x22\x0a\x20\x20\x20\x20\x20\x70\x61\
+\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\x66\x66\x66\x66\x66\
+\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x0a\
+\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\
+\x3a\x23\x30\x30\x66\x66\x30\x30\x22\x0a\x20\x20\x20\x20\x20\x69\
+\x64\x3d\x22\x70\x6f\x6c\x79\x67\x6f\x6e\x32\x22\x0a\x20\x20\x20\
+\x20\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x2e\x38\x36\x20\x32\
+\x20\x31\x36\x2e\x31\x34\x20\x32\x20\x32\x32\x20\x37\x2e\x38\x36\
+\x20\x32\x32\x20\x31\x36\x2e\x31\x34\x20\x31\x36\x2e\x31\x34\x20\
+\x32\x32\x20\x37\x2e\x38\x36\x20\x32\x32\x20\x32\x20\x31\x36\x2e\
+\x31\x34\x20\x32\x20\x37\x2e\x38\x36\x20\x37\x2e\x38\x36\x20\x32\
+\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\
+\x20\x20\x69\x64\x3d\x22\x6c\x69\x6e\x65\x34\x22\x0a\x20\x20\x20\
+\x20\x20\x79\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x78\
+\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\x22\
+\x38\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\x22\x20\
+\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\x20\x20\
+\x69\x64\x3d\x22\x6c\x69\x6e\x65\x36\x22\x0a\x20\x20\x20\x20\x20\
+\x79\x32\x3d\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x32\x3d\
+\x22\x31\x32\x2e\x30\x31\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\
+\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\
+\x22\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
+\x00\x00\x01\x90\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x72\x65\x66\x72\x65\
+\x73\x68\x2d\x63\x77\x22\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\
+\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x32\x33\x20\x34\x20\x32\x33\
+\x20\x31\x30\x20\x31\x37\x20\x31\x30\x22\x3e\x3c\x2f\x70\x6f\x6c\
+\x79\x6c\x69\x6e\x65\x3e\x3c\x70\x6f\x6c\x79\x6c\x69\x6e\x65\x20\
+\x70\x6f\x69\x6e\x74\x73\x3d\x22\x31\x20\x32\x30\x20\x31\x20\x31\
+\x34\x20\x37\x20\x31\x34\x22\x3e\x3c\x2f\x70\x6f\x6c\x79\x6c\x69\
+\x6e\x65\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x33\x2e\x35\
+\x31\x20\x39\x61\x39\x20\x39\x20\x30\x20\x30\x20\x31\x20\x31\x34\
+\x2e\x38\x35\x2d\x33\x2e\x33\x36\x4c\x32\x33\x20\x31\x30\x4d\x31\
+\x20\x31\x34\x6c\x34\x2e\x36\x34\x20\x34\x2e\x33\x36\x41\x39\x20\
+\x39\x20\x30\x20\x30\x20\x30\x20\x32\x30\x2e\x34\x39\x20\x31\x35\
+\x22\x3e\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x01\x76\
+\x3c\
+\x73\x76\x67\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\
+\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\
+\x30\x2f\x73\x76\x67\x22\x20\x77\x69\x64\x74\x68\x3d\x22\x32\x34\
+\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x32\x34\x22\x20\x76\x69\
+\x65\x77\x42\x6f\x78\x3d\x22\x30\x20\x30\x20\x32\x34\x20\x32\x34\
+\x22\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x77\x69\x64\x74\x68\
+\x3d\x22\x32\x22\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x63\x61\x70\x3d\x22\x72\x6f\x75\x6e\x64\x22\x20\x73\x74\x72\x6f\
+\x6b\x65\x2d\x6c\x69\x6e\x65\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\
+\x6e\x64\x22\x20\x63\x6c\x61\x73\x73\x3d\x22\x66\x65\x61\x74\x68\
+\x65\x72\x20\x66\x65\x61\x74\x68\x65\x72\x2d\x64\x65\x6c\x65\x74\
+\x65\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x32\x31\x20\
+\x34\x48\x38\x6c\x2d\x37\x20\x38\x20\x37\x20\x38\x68\x31\x33\x61\
+\x32\x20\x32\x20\x30\x20\x30\x20\x30\x20\x32\x2d\x32\x56\x36\x61\
+\x32\x20\x32\x20\x30\x20\x30\x20\x30\x2d\x32\x2d\x32\x7a\x22\x3e\
+\x3c\x2f\x70\x61\x74\x68\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\
+\x22\x31\x38\x22\x20\x79\x31\x3d\x22\x39\x22\x20\x78\x32\x3d\x22\
+\x31\x32\x22\x20\x79\x32\x3d\x22\x31\x35\x22\x3e\x3c\x2f\x6c\x69\
+\x6e\x65\x3e\x3c\x6c\x69\x6e\x65\x20\x78\x31\x3d\x22\x31\x32\x22\
+\x20\x79\x31\x3d\x22\x39\x22\x20\x78\x32\x3d\x22\x31\x38\x22\x20\
+\x79\x32\x3d\x22\x31\x35\x22\x3e\x3c\x2f\x6c\x69\x6e\x65\x3e\x3c\
+\x2f\x73\x76\x67\x3e\
+\x00\x00\x07\x9d\
+\x3c\
+\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\
+\x2d\x38\x22\x20\x73\x74\x61\x6e\x64\x61\x6c\x6f\x6e\x65\x3d\x22\
+\x6e\x6f\x22\x3f\x3e\x0a\x3c\x73\x76\x67\x0a\x20\x20\x20\x78\x6d\
+\x6c\x6e\x73\x3a\x64\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\
+\x75\x72\x6c\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x65\x6c\x65\x6d\x65\
+\x6e\x74\x73\x2f\x31\x2e\x31\x2f\x22\x0a\x20\x20\x20\x78\x6d\x6c\
+\x6e\x73\x3a\x63\x63\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x63\x72\
+\x65\x61\x74\x69\x76\x65\x63\x6f\x6d\x6d\x6f\x6e\x73\x2e\x6f\x72\
+\x67\x2f\x6e\x73\x23\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\
+\x72\x64\x66\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\
+\x77\x33\x2e\x6f\x72\x67\x2f\x31\x39\x39\x39\x2f\x30\x32\x2f\x32\
+\x32\x2d\x72\x64\x66\x2d\x73\x79\x6e\x74\x61\x78\x2d\x6e\x73\x23\
+\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x76\x67\x3d\x22\
+\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\
+\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x0a\x20\x20\x20\x78\
+\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\
+\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\
+\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\x73\x3a\x73\x6f\x64\x69\x70\
+\x6f\x64\x69\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x73\x6f\x64\x69\
+\x70\x6f\x64\x69\x2e\x73\x6f\x75\x72\x63\x65\x66\x6f\x72\x67\x65\
+\x2e\x6e\x65\x74\x2f\x44\x54\x44\x2f\x73\x6f\x64\x69\x70\x6f\x64\
+\x69\x2d\x30\x2e\x64\x74\x64\x22\x0a\x20\x20\x20\x78\x6d\x6c\x6e\
+\x73\x3a\x69\x6e\x6b\x73\x63\x61\x70\x65\x3d\x22\x68\x74\x74\x70\
+\x3a\x2f\x2f\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\x70\x65\x2e\
+\x6f\x72\x67\x2f\x6e\x61\x6d\x65\x73\x70\x61\x63\x65\x73\x2f\x69\
+\x6e\x6b\x73\x63\x61\x70\x65\x22\x0a\x20\x20\x20\x69\x6e\x6b\x73\
+\x63\x61\x70\x65\x3a\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\
+\x30\x72\x63\x31\x20\x28\x30\x39\x39\x36\x30\x64\x36\x66\x30\x35\
+\x2c\x20\x32\x30\x32\x30\x2d\x30\x34\x2d\x30\x39\x29\x22\x0a\x20\
+\x20\x20\x73\x6f\x64\x69\x70\x6f\x64\x69\x3a\x64\x6f\x63\x6e\x61\
+\x6d\x65\x3d\x22\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\
+\x6e\x2d\x72\x65\x64\x2e\x73\x76\x67\x22\x0a\x20\x20\x20\x69\x64\
+\x3d\x22\x73\x76\x67\x38\x22\x0a\x20\x20\x20\x76\x65\x72\x73\x69\
+\x6f\x6e\x3d\x22\x31\x2e\x31\x22\x0a\x20\x20\x20\x63\x6c\x61\x73\
+\x73\x3d\x22\x66\x65\x61\x74\x68\x65\x72\x20\x66\x65\x61\x74\x68\
+\x65\x72\x2d\x61\x6c\x65\x72\x74\x2d\x6f\x63\x74\x61\x67\x6f\x6e\
+\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\
+\x6a\x6f\x69\x6e\x3d\x22\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\
+\x73\x74\x72\x6f\x6b\x65\x2d\x6c\x69\x6e\x65\x63\x61\x70\x3d\x22\
+\x72\x6f\x75\x6e\x64\x22\x0a\x20\x20\x20\x73\x74\x72\x6f\x6b\x65\
+\x2d\x77\x69\x64\x74\x68\x3d\x22\x32\x22\x0a\x20\x20\x20\x73\x74\
+\x72\x6f\x6b\x65\x3d\x22\x63\x75\x72\x72\x65\x6e\x74\x43\x6f\x6c\
+\x6f\x72\x22\x0a\x20\x20\x20\x66\x69\x6c\x6c\x3d\x22\x6e\x6f\x6e\
+\x65\x22\x0a\x20\x20\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
+\x20\x30\x20\x32\x34\x20\x32\x34\x22\x0a\x20\x20\x20\x68\x65\x69\
+\x67\x68\x74\x3d\x22\x32\x34\x22\x0a\x20\x20\x20\x77\x69\x64\x74\
+\x68\x3d\x22\x32\x34\x22\x3e\x0a\x20\x20\x3c\x6d\x65\x74\x61\x64\
+\x61\x74\x61\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x6d\x65\x74\
+\x61\x64\x61\x74\x61\x31\x34\x22\x3e\x0a\x20\x20\x20\x20\x3c\x72\
+\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\x20\x20\x20\x20\x3c\x63\
+\x63\x3a\x57\x6f\x72\x6b\x0a\x20\x20\x20\x20\x20\x20\x20\x20\x20\
+\x72\x64\x66\x3a\x61\x62\x6f\x75\x74\x3d\x22\x22\x3e\x0a\x20\x20\
+\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\
+\x3e\x69\x6d\x61\x67\x65\x2f\x73\x76\x67\x2b\x78\x6d\x6c\x3c\x2f\
+\x64\x63\x3a\x66\x6f\x72\x6d\x61\x74\x3e\x0a\x20\x20\x20\x20\x20\
+\x20\x20\x20\x3c\x64\x63\x3a\x74\x79\x70\x65\x0a\x20\x20\x20\x20\
+\x20\x20\x20\x20\x20\x20\x20\x72\x64\x66\x3a\x72\x65\x73\x6f\x75\
+\x72\x63\x65\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x70\x75\x72\x6c\
+\x2e\x6f\x72\x67\x2f\x64\x63\x2f\x64\x63\x6d\x69\x74\x79\x70\x65\
+\x2f\x53\x74\x69\x6c\x6c\x49\x6d\x61\x67\x65\x22\x20\x2f\x3e\x0a\
+\x20\x20\x20\x20\x20\x20\x20\x20\x3c\x64\x63\x3a\x74\x69\x74\x6c\
+\x65\x3e\x3c\x2f\x64\x63\x3a\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\
+\x20\x20\x20\x20\x3c\x2f\x63\x63\x3a\x57\x6f\x72\x6b\x3e\x0a\x20\
+\x20\x20\x20\x3c\x2f\x72\x64\x66\x3a\x52\x44\x46\x3e\x0a\x20\x20\
+\x3c\x2f\x6d\x65\x74\x61\x64\x61\x74\x61\x3e\x0a\x20\x20\x3c\x64\
+\x65\x66\x73\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\x64\x65\x66\
+\x73\x31\x32\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x73\x6f\x64\x69\x70\
+\x6f\x64\x69\x3a\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x0a\x20\x20\
+\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x75\x72\x72\
+\x65\x6e\x74\x2d\x6c\x61\x79\x65\x72\x3d\x22\x73\x76\x67\x38\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
+\x69\x6e\x64\x6f\x77\x2d\x6d\x61\x78\x69\x6d\x69\x7a\x65\x64\x3d\
+\x22\x30\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\
+\x65\x3a\x77\x69\x6e\x64\x6f\x77\x2d\x79\x3d\x22\x32\x38\x31\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\
+\x69\x6e\x64\x6f\x77\x2d\x78\x3d\x22\x31\x34\x33\x33\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x79\x3d\
+\x22\x31\x32\x2e\x34\x30\x39\x37\x39\x37\x22\x0a\x20\x20\x20\x20\
+\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x63\x78\x3d\x22\x31\x32\
+\x2e\x38\x32\x38\x32\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x69\x6e\
+\x6b\x73\x63\x61\x70\x65\x3a\x7a\x6f\x6f\x6d\x3d\x22\x32\x31\x22\
+\x0a\x20\x20\x20\x20\x20\x73\x68\x6f\x77\x67\x72\x69\x64\x3d\x22\
+\x66\x61\x6c\x73\x65\x22\x0a\x20\x20\x20\x20\x20\x69\x64\x3d\x22\
+\x6e\x61\x6d\x65\x64\x76\x69\x65\x77\x31\x30\x22\x0a\x20\x20\x20\
+\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\x64\x6f\
+\x77\x2d\x68\x65\x69\x67\x68\x74\x3d\x22\x39\x35\x39\x22\x0a\x20\
+\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x77\x69\x6e\
+\x64\x6f\x77\x2d\x77\x69\x64\x74\x68\x3d\x22\x31\x35\x34\x32\x22\
+\x0a\x20\x20\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\
+\x61\x67\x65\x73\x68\x61\x64\x6f\x77\x3d\x22\x32\x22\x0a\x20\x20\
+\x20\x20\x20\x69\x6e\x6b\x73\x63\x61\x70\x65\x3a\x70\x61\x67\x65\
+\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x30\x22\x0a\x20\x20\x20\x20\
+\x20\x67\x75\x69\x64\x65\x74\x6f\x6c\x65\x72\x61\x6e\x63\x65\x3d\
+\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x67\x72\x69\x64\x74\x6f\
+\x6c\x65\x72\x61\x6e\x63\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\
+\x20\x20\x6f\x62\x6a\x65\x63\x74\x74\x6f\x6c\x65\x72\x61\x6e\x63\
+\x65\x3d\x22\x31\x30\x22\x0a\x20\x20\x20\x20\x20\x62\x6f\x72\x64\
+\x65\x72\x6f\x70\x61\x63\x69\x74\x79\x3d\x22\x31\x22\x0a\x20\x20\
+\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x63\x6f\x6c\x6f\x72\x3d\x22\
+\x23\x36\x36\x36\x36\x36\x36\x22\x0a\x20\x20\x20\x20\x20\x70\x61\
+\x67\x65\x63\x6f\x6c\x6f\x72\x3d\x22\x23\x66\x66\x66\x66\x66\x66\
+\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x70\x6f\x6c\x79\x67\x6f\x6e\x0a\
+\x20\x20\x20\x20\x20\x73\x74\x79\x6c\x65\x3d\x22\x66\x69\x6c\x6c\
+\x3a\x23\x66\x66\x30\x30\x30\x30\x22\x0a\x20\x20\x20\x20\x20\x69\
+\x64\x3d\x22\x70\x6f\x6c\x79\x67\x6f\x6e\x32\x22\x0a\x20\x20\x20\
+\x20\x20\x70\x6f\x69\x6e\x74\x73\x3d\x22\x37\x2e\x38\x36\x20\x32\
+\x20\x31\x36\x2e\x31\x34\x20\x32\x20\x32\x32\x20\x37\x2e\x38\x36\
+\x20\x32\x32\x20\x31\x36\x2e\x31\x34\x20\x31\x36\x2e\x31\x34\x20\
+\x32\x32\x20\x37\x2e\x38\x36\x20\x32\x32\x20\x32\x20\x31\x36\x2e\
+\x31\x34\x20\x32\x20\x37\x2e\x38\x36\x20\x37\x2e\x38\x36\x20\x32\
+\x22\x20\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\
+\x20\x20\x69\x64\x3d\x22\x6c\x69\x6e\x65\x34\x22\x0a\x20\x20\x20\
+\x20\x20\x79\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x78\
+\x32\x3d\x22\x31\x32\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\x22\
+\x38\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\x22\x20\
+\x2f\x3e\x0a\x20\x20\x3c\x6c\x69\x6e\x65\x0a\x20\x20\x20\x20\x20\
+\x69\x64\x3d\x22\x6c\x69\x6e\x65\x36\x22\x0a\x20\x20\x20\x20\x20\
+\x79\x32\x3d\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x32\x3d\
+\x22\x31\x32\x2e\x30\x31\x22\x0a\x20\x20\x20\x20\x20\x79\x31\x3d\
+\x22\x31\x36\x22\x0a\x20\x20\x20\x20\x20\x78\x31\x3d\x22\x31\x32\
+\x22\x20\x2f\x3e\x0a\x3c\x2f\x73\x76\x67\x3e\x0a\
+\x00\x00\x06\x4c\
+\x3c\
+\x73\x76\x67\x20\x77\x69\x64\x74\x68\x3d\x22\x38\x30\x22\x20\x68\
+\x65\x69\x67\x68\x74\x3d\x22\x38\x30\x22\x20\x78\x6d\x6c\x6e\x73\
+\x3d\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x77\x33\x2e\
+\x6f\x72\x67\x2f\x32\x30\x30\x30\x2f\x73\x76\x67\x22\x20\x66\x69\
+\x6c\x6c\x3d\x22\x6e\x6f\x6e\x65\x22\x3e\x0a\x20\x3c\x67\x3e\x0a\
+\x20\x20\x3c\x74\x69\x74\x6c\x65\x3e\x4c\x61\x79\x65\x72\x20\x31\
+\x3c\x2f\x74\x69\x74\x6c\x65\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\
+\x20\x66\x69\x6c\x6c\x2d\x72\x75\x6c\x65\x3d\x22\x65\x76\x65\x6e\
+\x6f\x64\x64\x22\x20\x63\x6c\x69\x70\x2d\x72\x75\x6c\x65\x3d\x22\
+\x65\x76\x65\x6e\x6f\x64\x64\x22\x20\x64\x3d\x22\x6d\x33\x33\x2e\
+\x39\x32\x35\x36\x2c\x39\x2e\x36\x39\x33\x32\x34\x63\x2d\x30\x2e\
+\x37\x36\x30\x38\x2c\x30\x20\x2d\x31\x2e\x34\x38\x30\x32\x2c\x30\
+\x2e\x33\x34\x36\x34\x33\x20\x2d\x31\x2e\x39\x35\x34\x35\x2c\x30\
+\x2e\x39\x34\x31\x32\x33\x6c\x2d\x35\x2e\x33\x32\x31\x2c\x36\x2e\
+\x36\x37\x32\x33\x6c\x2d\x31\x31\x2e\x36\x35\x30\x31\x2c\x30\x63\
+\x2d\x31\x2e\x33\x38\x30\x37\x2c\x30\x20\x2d\x32\x2e\x35\x2c\x31\
+\x2e\x31\x31\x39\x33\x20\x2d\x32\x2e\x35\x2c\x32\x2e\x35\x63\x30\
+\x2c\x31\x2e\x33\x38\x30\x37\x20\x31\x2e\x31\x31\x39\x33\x2c\x32\
+\x2e\x35\x20\x32\x2e\x35\x2c\x32\x2e\x35\x6c\x31\x2e\x35\x2c\x30\
+\x6c\x30\x2c\x34\x31\x2e\x35\x63\x30\x2c\x33\x2e\x35\x38\x39\x39\
+\x20\x32\x2e\x39\x31\x30\x31\x2c\x36\x2e\x35\x20\x36\x2e\x35\x2c\
+\x36\x2e\x35\x6c\x33\x34\x2c\x30\x63\x33\x2e\x35\x38\x39\x38\x2c\
+\x30\x20\x36\x2e\x35\x2c\x2d\x32\x2e\x39\x31\x30\x31\x20\x36\x2e\
+\x35\x2c\x2d\x36\x2e\x35\x6c\x30\x2c\x2d\x34\x31\x2e\x35\x6c\x31\
+\x2e\x35\x2c\x30\x63\x31\x2e\x33\x38\x30\x37\x2c\x30\x20\x32\x2e\
+\x35\x2c\x2d\x31\x2e\x31\x31\x39\x33\x20\x32\x2e\x35\x2c\x2d\x32\
+\x2e\x35\x63\x30\x2c\x2d\x31\x2e\x33\x38\x30\x37\x20\x2d\x31\x2e\
+\x31\x31\x39\x33\x2c\x2d\x32\x2e\x35\x20\x2d\x32\x2e\x35\x2c\x2d\
+\x32\x2e\x35\x6c\x2d\x31\x31\x2e\x36\x35\x30\x31\x2c\x30\x6c\x2d\
+\x35\x2e\x33\x32\x30\x39\x2c\x2d\x36\x2e\x36\x37\x32\x32\x63\x2d\
+\x30\x2e\x34\x37\x34\x34\x2c\x2d\x30\x2e\x35\x39\x34\x39\x20\x2d\
+\x31\x2e\x31\x39\x33\x38\x2c\x2d\x30\x2e\x39\x34\x31\x33\x33\x20\
+\x2d\x31\x2e\x39\x35\x34\x36\x2c\x2d\x30\x2e\x39\x34\x31\x33\x33\
+\x6c\x2d\x31\x32\x2e\x31\x34\x38\x38\x2c\x30\x7a\x6d\x2d\x30\x2e\
+\x39\x32\x35\x36\x2c\x31\x37\x2e\x36\x31\x33\x35\x33\x63\x31\x2e\
+\x33\x38\x30\x37\x2c\x30\x20\x32\x2e\x35\x2c\x31\x2e\x31\x31\x39\
+\x33\x20\x32\x2e\x35\x2c\x32\x2e\x35\x6c\x30\x2c\x32\x38\x63\x30\
+\x2c\x31\x2e\x33\x38\x30\x37\x20\x2d\x31\x2e\x31\x31\x39\x33\x2c\
+\x32\x2e\x35\x20\x2d\x32\x2e\x35\x2c\x32\x2e\x35\x63\x2d\x31\x2e\
+\x33\x38\x30\x37\x2c\x30\x20\x2d\x32\x2e\x35\x2c\x2d\x31\x2e\x31\
+\x31\x39\x33\x20\x2d\x32\x2e\x35\x2c\x2d\x32\x2e\x35\x6c\x30\x2c\
+\x2d\x32\x38\x63\x30\x2c\x2d\x31\x2e\x33\x38\x30\x37\x20\x31\x2e\
+\x31\x31\x39\x33\x2c\x2d\x32\x2e\x35\x20\x32\x2e\x35\x2c\x2d\x32\
+\x2e\x35\x7a\x6d\x31\x36\x2e\x35\x2c\x32\x2e\x35\x63\x30\x2c\x2d\
+\x31\x2e\x33\x38\x30\x37\x20\x2d\x31\x2e\x31\x31\x39\x33\x2c\x2d\
+\x32\x2e\x35\x20\x2d\x32\x2e\x35\x2c\x2d\x32\x2e\x35\x63\x2d\x31\
+\x2e\x33\x38\x30\x37\x2c\x30\x20\x2d\x32\x2e\x35\x2c\x31\x2e\x31\
+\x31\x39\x33\x20\x2d\x32\x2e\x35\x2c\x32\x2e\x35\x6c\x30\x2c\x32\
+\x38\x63\x30\x2c\x31\x2e\x33\x38\x30\x37\x20\x31\x2e\x31\x31\x39\
+\x33\x2c\x32\x2e\x35\x20\x32\x2e\x35\x2c\x32\x2e\x35\x63\x31\x2e\
+\x33\x38\x30\x37\x2c\x30\x20\x32\x2e\x35\x2c\x2d\x31\x2e\x31\x31\
+\x39\x33\x20\x32\x2e\x35\x2c\x2d\x32\x2e\x35\x6c\x30\x2c\x2d\x32\
+\x38\x7a\x6d\x2d\x32\x2e\x35\x34\x36\x34\x2c\x2d\x31\x32\x2e\x35\
+\x30\x31\x34\x6c\x2d\x32\x2e\x30\x38\x33\x32\x2c\x2d\x32\x2e\x36\
+\x31\x32\x31\x6c\x2d\x39\x2e\x37\x34\x30\x38\x2c\x30\x6c\x2d\x32\
+\x2e\x30\x38\x33\x32\x2c\x32\x2e\x36\x31\x32\x31\x6c\x31\x33\x2e\
+\x39\x30\x37\x32\x2c\x30\x7a\x22\x20\x66\x69\x6c\x6c\x3d\x22\x23\
+\x43\x32\x43\x43\x44\x45\x22\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\
+\x31\x22\x2f\x3e\x0a\x20\x20\x3c\x70\x61\x74\x68\x20\x66\x69\x6c\
+\x6c\x3d\x22\x23\x66\x66\x30\x30\x30\x30\x22\x20\x64\x3d\x22\x6d\
+\x37\x2e\x33\x33\x38\x33\x2c\x33\x39\x2e\x39\x39\x39\x39\x34\x6c\
+\x30\x2c\x30\x63\x30\x2c\x2d\x31\x38\x2e\x30\x38\x35\x33\x35\x20\
+\x31\x34\x2e\x36\x32\x33\x31\x36\x2c\x2d\x33\x32\x2e\x37\x34\x36\
+\x35\x31\x20\x33\x32\x2e\x36\x36\x31\x36\x37\x2c\x2d\x33\x32\x2e\
+\x37\x34\x36\x35\x31\x6c\x30\x2c\x30\x63\x38\x2e\x36\x36\x32\x35\
+\x33\x2c\x30\x20\x31\x36\x2e\x39\x37\x30\x32\x35\x2c\x33\x2e\x34\
+\x35\x30\x30\x38\x20\x32\x33\x2e\x30\x39\x35\x33\x32\x2c\x39\x2e\
+\x35\x39\x31\x32\x36\x63\x36\x2e\x31\x32\x35\x33\x31\x2c\x36\x2e\
+\x31\x34\x31\x31\x39\x20\x39\x2e\x35\x36\x36\x34\x32\x2c\x31\x34\
+\x2e\x34\x37\x30\x34\x34\x20\x39\x2e\x35\x36\x36\x34\x32\x2c\x32\
+\x33\x2e\x31\x35\x35\x32\x34\x6c\x30\x2c\x30\x63\x30\x2c\x31\x38\
+\x2e\x30\x38\x35\x35\x36\x20\x2d\x31\x34\x2e\x36\x32\x33\x30\x35\
+\x2c\x33\x32\x2e\x37\x34\x36\x36\x33\x20\x2d\x33\x32\x2e\x36\x36\
+\x31\x37\x33\x2c\x33\x32\x2e\x37\x34\x36\x36\x33\x6c\x30\x2c\x30\
+\x63\x2d\x31\x38\x2e\x30\x33\x38\x35\x31\x2c\x30\x20\x2d\x33\x32\
+\x2e\x36\x36\x31\x36\x37\x2c\x2d\x31\x34\x2e\x36\x36\x31\x30\x36\
+\x20\x2d\x33\x32\x2e\x36\x36\x31\x36\x37\x2c\x2d\x33\x32\x2e\x37\
+\x34\x36\x36\x33\x63\x30\x2c\x30\x20\x30\x2c\x30\x20\x30\x2c\x30\
+\x6c\x2d\x30\x2e\x30\x30\x30\x30\x31\x2c\x30\x7a\x6d\x35\x32\x2e\
+\x37\x34\x31\x32\x31\x2c\x31\x34\x2e\x36\x34\x39\x31\x36\x6c\x30\
+\x2c\x30\x63\x37\x2e\x31\x39\x31\x30\x32\x2c\x2d\x39\x2e\x39\x30\
+\x38\x33\x34\x20\x36\x2e\x31\x32\x32\x30\x39\x2c\x2d\x32\x33\x2e\
+\x35\x38\x39\x34\x38\x20\x2d\x32\x2e\x35\x32\x30\x31\x35\x2c\x2d\
+\x33\x32\x2e\x32\x35\x34\x30\x37\x63\x2d\x38\x2e\x36\x34\x32\x32\
+\x35\x2c\x2d\x38\x2e\x36\x36\x34\x36\x36\x20\x2d\x32\x32\x2e\x32\
+\x38\x37\x39\x33\x2c\x2d\x39\x2e\x37\x33\x36\x33\x37\x20\x2d\x33\
+\x32\x2e\x31\x37\x30\x33\x33\x2c\x2d\x32\x2e\x35\x32\x36\x35\x38\
+\x6c\x33\x34\x2e\x36\x39\x30\x34\x37\x2c\x33\x34\x2e\x37\x38\x30\
+\x36\x35\x6c\x30\x2e\x30\x30\x30\x30\x31\x2c\x30\x7a\x6d\x2d\x34\
+\x30\x2e\x31\x35\x38\x38\x39\x2c\x2d\x32\x39\x2e\x32\x39\x38\x30\
+\x31\x63\x2d\x37\x2e\x31\x39\x31\x30\x37\x2c\x39\x2e\x39\x30\x38\
+\x32\x38\x20\x2d\x36\x2e\x31\x32\x32\x31\x37\x2c\x32\x33\x2e\x35\
+\x38\x39\x34\x33\x20\x32\x2e\x35\x32\x30\x30\x32\x2c\x33\x32\x2e\
+\x32\x35\x33\x39\x63\x38\x2e\x36\x34\x32\x31\x37\x2c\x38\x2e\x36\
+\x36\x34\x37\x31\x20\x32\x32\x2e\x32\x38\x37\x38\x35\x2c\x39\x2e\
+\x37\x33\x36\x34\x31\x20\x33\x32\x2e\x31\x37\x30\x32\x35\x2c\x32\
+\x2e\x35\x32\x36\x37\x6c\x2d\x33\x34\x2e\x36\x39\x30\x32\x38\x2c\
+\x2d\x33\x34\x2e\x37\x38\x30\x35\x39\x6c\x30\x2c\x30\x6c\x30\x2e\
+\x30\x30\x30\x30\x31\x2c\x2d\x30\x2e\x30\x30\x30\x30\x31\x7a\x22\
+\x20\x69\x64\x3d\x22\x73\x76\x67\x5f\x33\x22\x2f\x3e\x0a\x20\x3c\
+\x2f\x67\x3e\x0a\x0a\x3c\x2f\x73\x76\x67\x3e\
+\x00\x00\x09\x97\
+\x3c\
+\x73\x76\x67\x20\x66\x69\x6c\x6c\x3d\x22\x23\x30\x30\x30\x30\x30\
+\x30\x22\x20\x78\x6d\x6c\x6e\x73\x3d\x22\x68\x74\x74\x70\x3a\x2f\
+\x2f\x77\x77\x77\x2e\x77\x33\x2e\x6f\x72\x67\x2f\x32\x30\x30\x30\
+\x2f\x73\x76\x67\x22\x20\x76\x69\x65\x77\x42\x6f\x78\x3d\x22\x30\
+\x20\x30\x20\x35\x30\x20\x35\x30\x22\x20\x77\x69\x64\x74\x68\x3d\
+\x22\x35\x30\x70\x78\x22\x20\x68\x65\x69\x67\x68\x74\x3d\x22\x35\
+\x30\x70\x78\x22\x3e\x3c\x70\x61\x74\x68\x20\x64\x3d\x22\x4d\x20\
+\x32\x35\x20\x32\x20\x43\x20\x32\x30\x2e\x39\x34\x31\x34\x30\x36\
+\x20\x32\x20\x31\x38\x2e\x31\x38\x37\x35\x20\x32\x2e\x39\x36\x38\
+\x37\x35\x20\x31\x36\x2e\x34\x33\x37\x35\x20\x34\x2e\x33\x37\x35\
+\x20\x43\x20\x31\x34\x2e\x36\x38\x37\x35\x20\x35\x2e\x37\x38\x31\
+\x32\x35\x20\x31\x34\x20\x37\x2e\x35\x38\x39\x38\x34\x34\x20\x31\
+\x34\x20\x39\x2e\x30\x39\x33\x37\x35\x20\x4c\x20\x31\x34\x20\x31\
+\x34\x20\x4c\x20\x32\x34\x20\x31\x34\x20\x4c\x20\x32\x34\x20\x31\
+\x35\x20\x4c\x20\x39\x2e\x30\x39\x33\x37\x35\x20\x31\x35\x20\x43\
+\x20\x37\x2e\x32\x36\x35\x36\x32\x35\x20\x31\x35\x20\x35\x2e\x34\
+\x31\x30\x31\x35\x36\x20\x31\x35\x2e\x37\x39\x32\x39\x36\x39\x20\
+\x34\x2e\x30\x39\x33\x37\x35\x20\x31\x37\x2e\x34\x36\x38\x37\x35\
+\x20\x43\x20\x32\x2e\x37\x37\x37\x33\x34\x34\x20\x31\x39\x2e\x31\
+\x34\x34\x35\x33\x31\x20\x32\x20\x32\x31\x2e\x36\x34\x34\x35\x33\
+\x31\x20\x32\x20\x32\x35\x20\x43\x20\x32\x20\x32\x38\x2e\x33\x35\
+\x35\x34\x36\x39\x20\x32\x2e\x37\x37\x37\x33\x34\x34\x20\x33\x30\
+\x2e\x38\x35\x35\x34\x36\x39\x20\x34\x2e\x30\x39\x33\x37\x35\x20\
+\x33\x32\x2e\x35\x33\x31\x32\x35\x20\x43\x20\x35\x2e\x34\x31\x30\
+\x31\x35\x36\x20\x33\x34\x2e\x32\x30\x37\x30\x33\x31\x20\x37\x2e\
+\x32\x36\x35\x36\x32\x35\x20\x33\x35\x20\x39\x2e\x30\x39\x33\x37\
+\x35\x20\x33\x35\x20\x4c\x20\x31\x34\x20\x33\x35\x20\x4c\x20\x31\
+\x34\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x43\x20\x31\x34\x20\
+\x34\x32\x2e\x34\x31\x30\x31\x35\x36\x20\x31\x34\x2e\x36\x38\x37\
+\x35\x20\x34\x34\x2e\x32\x31\x38\x37\x35\x20\x31\x36\x2e\x34\x33\
+\x37\x35\x20\x34\x35\x2e\x36\x32\x35\x20\x43\x20\x31\x38\x2e\x31\
+\x38\x37\x35\x20\x34\x37\x2e\x30\x33\x31\x32\x35\x20\x32\x30\x2e\
+\x39\x34\x31\x34\x30\x36\x20\x34\x38\x20\x32\x35\x20\x34\x38\x20\
+\x43\x20\x32\x39\x2e\x30\x35\x38\x35\x39\x34\x20\x34\x38\x20\x33\
+\x31\x2e\x38\x31\x32\x35\x20\x34\x37\x2e\x30\x33\x31\x32\x35\x20\
+\x33\x33\x2e\x35\x36\x32\x35\x20\x34\x35\x2e\x36\x32\x35\x20\x43\
+\x20\x33\x35\x2e\x33\x31\x32\x35\x20\x34\x34\x2e\x32\x31\x38\x37\
+\x35\x20\x33\x36\x20\x34\x32\x2e\x34\x31\x30\x31\x35\x36\x20\x33\
+\x36\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x4c\x20\x33\x36\x20\
+\x33\x36\x20\x4c\x20\x32\x36\x20\x33\x36\x20\x4c\x20\x32\x36\x20\
+\x33\x35\x20\x4c\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x33\x35\
+\x20\x43\x20\x34\x32\x2e\x37\x33\x34\x33\x37\x35\x20\x33\x35\x20\
+\x34\x34\x2e\x35\x38\x39\x38\x34\x34\x20\x33\x34\x2e\x32\x30\x37\
+\x30\x33\x31\x20\x34\x35\x2e\x39\x30\x36\x32\x35\x20\x33\x32\x2e\
+\x35\x33\x31\x32\x35\x20\x43\x20\x34\x37\x2e\x32\x32\x32\x36\x35\
+\x36\x20\x33\x30\x2e\x38\x35\x35\x34\x36\x39\x20\x34\x38\x20\x32\
+\x38\x2e\x33\x35\x35\x34\x36\x39\x20\x34\x38\x20\x32\x35\x20\x43\
+\x20\x34\x38\x20\x32\x31\x2e\x36\x34\x34\x35\x33\x31\x20\x34\x37\
+\x2e\x32\x32\x32\x36\x35\x36\x20\x31\x39\x2e\x31\x34\x34\x35\x33\
+\x31\x20\x34\x35\x2e\x39\x30\x36\x32\x35\x20\x31\x37\x2e\x34\x36\
+\x38\x37\x35\x20\x43\x20\x34\x34\x2e\x35\x38\x39\x38\x34\x34\x20\
+\x31\x35\x2e\x37\x39\x32\x39\x36\x39\x20\x34\x32\x2e\x37\x33\x34\
+\x33\x37\x35\x20\x31\x35\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\
+\x31\x35\x20\x4c\x20\x33\x36\x20\x31\x35\x20\x4c\x20\x33\x36\x20\
+\x39\x2e\x30\x39\x33\x37\x35\x20\x43\x20\x33\x36\x20\x37\x2e\x35\
+\x35\x30\x37\x38\x31\x20\x33\x35\x2e\x33\x31\x36\x34\x30\x36\x20\
+\x35\x2e\x37\x33\x38\x32\x38\x31\x20\x33\x33\x2e\x35\x36\x32\x35\
+\x20\x34\x2e\x33\x34\x33\x37\x35\x20\x43\x20\x33\x31\x2e\x38\x30\
+\x38\x35\x39\x34\x20\x32\x2e\x39\x34\x39\x32\x31\x39\x20\x32\x39\
+\x2e\x30\x35\x34\x36\x38\x38\x20\x32\x20\x32\x35\x20\x32\x20\x5a\
+\x20\x4d\x20\x32\x35\x20\x34\x20\x43\x20\x32\x38\x2e\x37\x34\x36\
+\x30\x39\x34\x20\x34\x20\x33\x31\x2e\x30\x31\x35\x36\x32\x35\x20\
+\x34\x2e\x38\x37\x35\x20\x33\x32\x2e\x33\x31\x32\x35\x20\x35\x2e\
+\x39\x30\x36\x32\x35\x20\x43\x20\x33\x33\x2e\x36\x30\x39\x33\x37\
+\x35\x20\x36\x2e\x39\x33\x37\x35\x20\x33\x34\x20\x38\x2e\x31\x33\
+\x36\x37\x31\x39\x20\x33\x34\x20\x39\x2e\x30\x39\x33\x37\x35\x20\
+\x4c\x20\x33\x34\x20\x32\x31\x20\x43\x20\x33\x34\x20\x32\x32\x2e\
+\x36\x35\x36\x32\x35\x20\x33\x32\x2e\x36\x35\x36\x32\x35\x20\x32\
+\x34\x20\x33\x31\x20\x32\x34\x20\x4c\x20\x31\x39\x20\x32\x34\x20\
+\x43\x20\x31\x36\x2e\x39\x34\x31\x34\x30\x36\x20\x32\x34\x20\x31\
+\x35\x2e\x31\x36\x37\x39\x36\x39\x20\x32\x35\x2e\x32\x36\x39\x35\
+\x33\x31\x20\x31\x34\x2e\x34\x30\x36\x32\x35\x20\x32\x37\x2e\x30\
+\x36\x32\x35\x20\x43\x20\x31\x34\x2e\x32\x37\x37\x33\x34\x34\x20\
+\x32\x37\x2e\x33\x35\x39\x33\x37\x35\x20\x31\x34\x2e\x31\x36\x30\
+\x31\x35\x36\x20\x32\x37\x2e\x36\x37\x35\x37\x38\x31\x20\x31\x34\
+\x2e\x30\x39\x33\x37\x35\x20\x32\x38\x20\x43\x20\x31\x34\x2e\x30\
+\x32\x37\x33\x34\x34\x20\x32\x38\x2e\x33\x32\x34\x32\x31\x39\x20\
+\x31\x34\x20\x32\x38\x2e\x36\x35\x36\x32\x35\x20\x31\x34\x20\x32\
+\x39\x20\x4c\x20\x31\x34\x20\x33\x33\x20\x4c\x20\x39\x2e\x30\x39\
+\x33\x37\x35\x20\x33\x33\x20\x43\x20\x37\x2e\x38\x32\x34\x32\x31\
+\x39\x20\x33\x33\x20\x36\x2e\x36\x34\x38\x34\x33\x38\x20\x33\x32\
+\x2e\x35\x30\x33\x39\x30\x36\x20\x35\x2e\x36\x38\x37\x35\x20\x33\
+\x31\x2e\x32\x38\x31\x32\x35\x20\x43\x20\x34\x2e\x37\x32\x36\x35\
+\x36\x33\x20\x33\x30\x2e\x30\x35\x38\x35\x39\x34\x20\x34\x20\x32\
+\x38\x2e\x30\x34\x32\x39\x36\x39\x20\x34\x20\x32\x35\x20\x43\x20\
+\x34\x20\x32\x31\x2e\x39\x35\x37\x30\x33\x31\x20\x34\x2e\x37\x32\
+\x36\x35\x36\x33\x20\x31\x39\x2e\x39\x34\x31\x34\x30\x36\x20\x35\
+\x2e\x36\x38\x37\x35\x20\x31\x38\x2e\x37\x31\x38\x37\x35\x20\x43\
+\x20\x36\x2e\x36\x34\x38\x34\x33\x38\x20\x31\x37\x2e\x34\x39\x36\
+\x30\x39\x34\x20\x37\x2e\x38\x32\x34\x32\x31\x39\x20\x31\x37\x20\
+\x39\x2e\x30\x39\x33\x37\x35\x20\x31\x37\x20\x4c\x20\x32\x36\x20\
+\x31\x37\x20\x4c\x20\x32\x36\x20\x31\x32\x20\x4c\x20\x31\x36\x20\
+\x31\x32\x20\x4c\x20\x31\x36\x20\x39\x2e\x30\x39\x33\x37\x35\x20\
+\x43\x20\x31\x36\x20\x38\x2e\x31\x39\x39\x32\x31\x39\x20\x31\x36\
+\x2e\x33\x38\x36\x37\x31\x39\x20\x36\x2e\x39\x38\x30\x34\x36\x39\
+\x20\x31\x37\x2e\x36\x38\x37\x35\x20\x35\x2e\x39\x33\x37\x35\x20\
+\x43\x20\x31\x38\x2e\x39\x38\x38\x32\x38\x31\x20\x34\x2e\x38\x39\
+\x34\x35\x33\x31\x20\x32\x31\x2e\x32\x35\x37\x38\x31\x33\x20\x34\
+\x20\x32\x35\x20\x34\x20\x5a\x20\x4d\x20\x32\x30\x20\x37\x20\x43\
+\x20\x31\x38\x2e\x38\x39\x38\x34\x33\x38\x20\x37\x20\x31\x38\x20\
+\x37\x2e\x38\x39\x38\x34\x33\x38\x20\x31\x38\x20\x39\x20\x43\x20\
+\x31\x38\x20\x31\x30\x2e\x31\x30\x31\x35\x36\x33\x20\x31\x38\x2e\
+\x38\x39\x38\x34\x33\x38\x20\x31\x31\x20\x32\x30\x20\x31\x31\x20\
+\x43\x20\x32\x31\x2e\x31\x30\x31\x35\x36\x33\x20\x31\x31\x20\x32\
+\x32\x20\x31\x30\x2e\x31\x30\x31\x35\x36\x33\x20\x32\x32\x20\x39\
+\x20\x43\x20\x32\x32\x20\x37\x2e\x38\x39\x38\x34\x33\x38\x20\x32\
+\x31\x2e\x31\x30\x31\x35\x36\x33\x20\x37\x20\x32\x30\x20\x37\x20\
+\x5a\x20\x4d\x20\x33\x36\x20\x31\x37\x20\x4c\x20\x34\x30\x2e\x39\
+\x30\x36\x32\x35\x20\x31\x37\x20\x43\x20\x34\x32\x2e\x31\x37\x35\
+\x37\x38\x31\x20\x31\x37\x20\x34\x33\x2e\x33\x35\x31\x35\x36\x33\
+\x20\x31\x37\x2e\x34\x39\x36\x30\x39\x34\x20\x34\x34\x2e\x33\x31\
+\x32\x35\x20\x31\x38\x2e\x37\x31\x38\x37\x35\x20\x43\x20\x34\x35\
+\x2e\x32\x37\x33\x34\x33\x38\x20\x31\x39\x2e\x39\x34\x31\x34\x30\
+\x36\x20\x34\x36\x20\x32\x31\x2e\x39\x35\x37\x30\x33\x31\x20\x34\
+\x36\x20\x32\x35\x20\x43\x20\x34\x36\x20\x32\x38\x2e\x30\x34\x32\
+\x39\x36\x39\x20\x34\x35\x2e\x32\x37\x33\x34\x33\x38\x20\x33\x30\
+\x2e\x30\x35\x38\x35\x39\x34\x20\x34\x34\x2e\x33\x31\x32\x35\x20\
+\x33\x31\x2e\x32\x38\x31\x32\x35\x20\x43\x20\x34\x33\x2e\x33\x35\
+\x31\x35\x36\x33\x20\x33\x32\x2e\x35\x30\x33\x39\x30\x36\x20\x34\
+\x32\x2e\x31\x37\x35\x37\x38\x31\x20\x33\x33\x20\x34\x30\x2e\x39\
+\x30\x36\x32\x35\x20\x33\x33\x20\x4c\x20\x32\x34\x20\x33\x33\x20\
+\x4c\x20\x32\x34\x20\x33\x38\x20\x4c\x20\x33\x34\x20\x33\x38\x20\
+\x4c\x20\x33\x34\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x43\x20\
+\x33\x34\x20\x34\x31\x2e\x38\x30\x30\x37\x38\x31\x20\x33\x33\x2e\
+\x36\x31\x33\x32\x38\x31\x20\x34\x33\x2e\x30\x31\x39\x35\x33\x31\
+\x20\x33\x32\x2e\x33\x31\x32\x35\x20\x34\x34\x2e\x30\x36\x32\x35\
+\x20\x43\x20\x33\x31\x2e\x30\x31\x31\x37\x31\x39\x20\x34\x35\x2e\
+\x31\x30\x35\x34\x36\x39\x20\x32\x38\x2e\x37\x34\x32\x31\x38\x38\
+\x20\x34\x36\x20\x32\x35\x20\x34\x36\x20\x43\x20\x32\x31\x2e\x32\
+\x35\x37\x38\x31\x33\x20\x34\x36\x20\x31\x38\x2e\x39\x38\x38\x32\
+\x38\x31\x20\x34\x35\x2e\x31\x30\x35\x34\x36\x39\x20\x31\x37\x2e\
+\x36\x38\x37\x35\x20\x34\x34\x2e\x30\x36\x32\x35\x20\x43\x20\x31\
+\x36\x2e\x33\x38\x36\x37\x31\x39\x20\x34\x33\x2e\x30\x31\x39\x35\
+\x33\x31\x20\x31\x36\x20\x34\x31\x2e\x38\x30\x30\x37\x38\x31\x20\
+\x31\x36\x20\x34\x30\x2e\x39\x30\x36\x32\x35\x20\x4c\x20\x31\x36\
+\x20\x32\x39\x20\x43\x20\x31\x36\x20\x32\x38\x2e\x37\x39\x32\x39\
+\x36\x39\x20\x31\x36\x2e\x30\x32\x33\x34\x33\x38\x20\x32\x38\x2e\
+\x36\x30\x31\x35\x36\x33\x20\x31\x36\x2e\x30\x36\x32\x35\x20\x32\
+\x38\x2e\x34\x30\x36\x32\x35\x20\x43\x20\x31\x36\x2e\x33\x34\x33\
+\x37\x35\x20\x32\x37\x2e\x30\x33\x39\x30\x36\x33\x20\x31\x37\x2e\
+\x35\x35\x30\x37\x38\x31\x20\x32\x36\x20\x31\x39\x20\x32\x36\x20\
+\x4c\x20\x33\x31\x20\x32\x36\x20\x43\x20\x33\x33\x2e\x37\x34\x36\
+\x30\x39\x34\x20\x32\x36\x20\x33\x36\x20\x32\x33\x2e\x37\x34\x36\
+\x30\x39\x34\x20\x33\x36\x20\x32\x31\x20\x5a\x20\x4d\x20\x33\x30\
+\x20\x33\x39\x20\x43\x20\x32\x38\x2e\x38\x39\x38\x34\x33\x38\x20\
+\x33\x39\x20\x32\x38\x20\x33\x39\x2e\x38\x39\x38\x34\x33\x38\x20\
+\x32\x38\x20\x34\x31\x20\x43\x20\x32\x38\x20\x34\x32\x2e\x31\x30\
+\x31\x35\x36\x33\x20\x32\x38\x2e\x38\x39\x38\x34\x33\x38\x20\x34\
+\x33\x20\x33\x30\x20\x34\x33\x20\x43\x20\x33\x31\x2e\x31\x30\x31\
+\x35\x36\x33\x20\x34\x33\x20\x33\x32\x20\x34\x32\x2e\x31\x30\x31\
+\x35\x36\x33\x20\x33\x32\x20\x34\x31\x20\x43\x20\x33\x32\x20\x33\
+\x39\x2e\x38\x39\x38\x34\x33\x38\x20\x33\x31\x2e\x31\x30\x31\x35\
+\x36\x33\x20\x33\x39\x20\x33\x30\x20\x33\x39\x20\x5a\x22\x2f\x3e\
+\x3c\x2f\x73\x76\x67\x3e\
+"
+
+qt_resource_name = b"\
+\x00\x09\
+\x00\x28\xbf\x23\
+\x00\x73\
+\x00\x74\x00\x79\x00\x6c\x00\x65\x00\x2e\x00\x63\x00\x73\x00\x73\
+\x00\x05\
+\x00\x6f\xa6\x53\
+\x00\x69\
+\x00\x63\x00\x6f\x00\x6e\x00\x73\
+\x00\x0f\
+\x00\x50\xd7\x47\
+\x00\x70\
+\x00\x6c\x00\x75\x00\x73\x00\x2d\x00\x73\x00\x71\x00\x75\x00\x61\x00\x72\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0c\
+\x02\x33\x2a\x87\
+\x00\x6e\
+\x00\x6f\x00\x2d\x00\x61\x00\x6c\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x10\
+\x03\xdc\xdd\x87\
+\x00\x73\
+\x00\x74\x00\x61\x00\x72\x00\x2d\x00\x63\x00\x72\x00\x6f\x00\x73\x00\x73\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x08\
+\x05\x77\x54\xa7\
+\x00\x6c\
+\x00\x6f\x00\x61\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x08\
+\x05\xa8\x57\x87\
+\x00\x63\
+\x00\x6f\x00\x64\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0a\
+\x08\x4a\xc4\x07\
+\x00\x65\
+\x00\x78\x00\x70\x00\x61\x00\x6e\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x09\
+\x08\x9b\xad\xc7\
+\x00\x74\
+\x00\x72\x00\x61\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x08\
+\x08\xc8\x55\xe7\
+\x00\x73\
+\x00\x61\x00\x76\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x07\
+\x09\xc7\x5a\x27\
+\x00\x73\
+\x00\x65\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x08\
+\x0a\x85\x55\x87\
+\x00\x73\
+\x00\x74\x00\x61\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0a\
+\x0a\xc8\xf6\x87\
+\x00\x66\
+\x00\x6f\x00\x6c\x00\x64\x00\x65\x00\x72\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0c\
+\x0a\xdc\x3f\xc7\
+\x00\x63\
+\x00\x6f\x00\x6c\x00\x6c\x00\x61\x00\x70\x00\x73\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0f\
+\x0b\x14\x80\xa7\
+\x00\x67\
+\x00\x72\x00\x65\x00\x65\x00\x6e\x00\x2d\x00\x61\x00\x6c\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0b\
+\x0c\x6a\x21\xc7\
+\x00\x72\
+\x00\x65\x00\x66\x00\x72\x00\x65\x00\x73\x00\x68\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0a\
+\x0c\xad\x02\x87\
+\x00\x64\
+\x00\x65\x00\x6c\x00\x65\x00\x74\x00\x65\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x0d\
+\x0d\xf9\x2b\x67\
+\x00\x72\
+\x00\x65\x00\x64\x00\x2d\x00\x61\x00\x6c\x00\x65\x00\x72\x00\x74\x00\x2e\x00\x73\x00\x76\x00\x67\
+\x00\x11\
+\x0e\x2c\x55\xe7\
+\x00\x74\
+\x00\x72\x00\x61\x00\x73\x00\x68\x00\x2d\x00\x63\x00\x72\x00\x6f\x00\x73\x00\x73\x00\x65\x00\x64\x00\x2e\x00\x73\x00\x76\x00\x67\
+\
+\x00\x0a\
+\x0f\x6e\x5b\x87\
+\x00\x70\
+\x00\x79\x00\x74\x00\x68\x00\x6f\x00\x6e\x00\x2e\x00\x73\x00\x76\x00\x67\
+"
+
+qt_resource_struct_v1 = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x00\x18\x00\x02\x00\x00\x00\x12\x00\x00\x00\x03\
+\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\
+\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x01\x7d\
+\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x21\
+\x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xfe\
+\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x6e\
+\x00\x00\x00\xbc\x00\x01\x00\x00\x00\x01\x00\x00\x0d\xa5\
+\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x11\x51\
+\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf0\
+\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00\x16\x7c\
+\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x17\xf0\
+\x00\x00\x01\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xae\
+\x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x22\x74\
+\x00\x00\x01\x66\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x07\
+\x00\x00\x01\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x33\xa8\
+\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x35\x3c\
+\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x36\xb6\
+\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x57\
+\x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x44\xa7\
+"
+
+qt_resource_struct_v2 = b"\
+\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
+\x00\x00\x00\x18\x00\x02\x00\x00\x00\x12\x00\x00\x00\x03\
+\x00\x00\x00\x00\x00\x00\x00\x00\
+\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x04\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+\x00\x00\x00\x4c\x00\x00\x00\x00\x00\x01\x00\x00\x01\x7d\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
+\x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x21\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+\x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x0a\xfe\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+\x00\x00\x00\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x6e\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
+\x00\x00\x00\xbc\x00\x01\x00\x00\x00\x01\x00\x00\x0d\xa5\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
+\x00\x00\x00\xd6\x00\x00\x00\x00\x00\x01\x00\x00\x11\x51\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
+\x00\x00\x00\xee\x00\x00\x00\x00\x00\x01\x00\x00\x14\xf0\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+\x00\x00\x01\x04\x00\x00\x00\x00\x00\x01\x00\x00\x16\x7c\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00\x17\xf0\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
+\x00\x00\x01\x2e\x00\x00\x00\x00\x00\x01\x00\x00\x1d\xae\
+\x00\x00\x01\x9a\x4b\xc3\x1d\x94\
+\x00\x00\x01\x48\x00\x00\x00\x00\x00\x01\x00\x00\x22\x74\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
+\x00\x00\x01\x66\x00\x00\x00\x00\x00\x01\x00\x00\x2c\x07\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd2\
+\x00\x00\x01\x8a\x00\x00\x00\x00\x00\x01\x00\x00\x33\xa8\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+\x00\x00\x01\xa6\x00\x00\x00\x00\x00\x01\x00\x00\x35\x3c\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
+\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00\x36\xb6\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd3\
+\x00\x00\x01\xe0\x00\x00\x00\x00\x00\x01\x00\x00\x3e\x57\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd5\
+\x00\x00\x02\x08\x00\x00\x00\x00\x00\x01\x00\x00\x44\xa7\
+\x00\x00\x01\x95\xe8\xaa\xa9\xd4\
+"
+
+qt_version = [int(v) for v in QtCore.qVersion().split(".")]
+if qt_version < [5, 8, 0]:
+ rcc_version = 1
+ qt_resource_struct = qt_resource_struct_v1
+else:
+ rcc_version = 2
+ qt_resource_struct = qt_resource_struct_v2
+
+
+def qInitResources():
+ QtCore.qRegisterResourceData(
+ rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
+
+def qCleanupResources():
+ QtCore.qUnregisterResourceData(
+ rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data
+ )
+
+
+qInitResources()
diff --git a/instrumentserver/resource.qrc b/src/instrumentserver/resource.qrc
similarity index 100%
rename from instrumentserver/resource.qrc
rename to src/instrumentserver/resource.qrc
diff --git a/instrumentserver/resource/icons/alert-octagon-green.svg b/src/instrumentserver/resource/icons/alert-octagon-green.svg
similarity index 100%
rename from instrumentserver/resource/icons/alert-octagon-green.svg
rename to src/instrumentserver/resource/icons/alert-octagon-green.svg
diff --git a/instrumentserver/resource/icons/alert-octagon-red.svg b/src/instrumentserver/resource/icons/alert-octagon-red.svg
similarity index 100%
rename from instrumentserver/resource/icons/alert-octagon-red.svg
rename to src/instrumentserver/resource/icons/alert-octagon-red.svg
diff --git a/instrumentserver/resource/icons/alert-octagon.svg b/src/instrumentserver/resource/icons/alert-octagon.svg
similarity index 100%
rename from instrumentserver/resource/icons/alert-octagon.svg
rename to src/instrumentserver/resource/icons/alert-octagon.svg
diff --git a/instrumentserver/resource/icons/client_app_icon.svg b/src/instrumentserver/resource/icons/client_app_icon.svg
similarity index 100%
rename from instrumentserver/resource/icons/client_app_icon.svg
rename to src/instrumentserver/resource/icons/client_app_icon.svg
diff --git a/instrumentserver/resource/icons/code.svg b/src/instrumentserver/resource/icons/code.svg
similarity index 100%
rename from instrumentserver/resource/icons/code.svg
rename to src/instrumentserver/resource/icons/code.svg
diff --git a/instrumentserver/resource/icons/collapse.svg b/src/instrumentserver/resource/icons/collapse.svg
similarity index 100%
rename from instrumentserver/resource/icons/collapse.svg
rename to src/instrumentserver/resource/icons/collapse.svg
diff --git a/instrumentserver/resource/icons/delete.svg b/src/instrumentserver/resource/icons/delete.svg
similarity index 100%
rename from instrumentserver/resource/icons/delete.svg
rename to src/instrumentserver/resource/icons/delete.svg
diff --git a/instrumentserver/resource/icons/expand.svg b/src/instrumentserver/resource/icons/expand.svg
similarity index 100%
rename from instrumentserver/resource/icons/expand.svg
rename to src/instrumentserver/resource/icons/expand.svg
diff --git a/instrumentserver/resource/icons/folder.svg b/src/instrumentserver/resource/icons/folder.svg
similarity index 100%
rename from instrumentserver/resource/icons/folder.svg
rename to src/instrumentserver/resource/icons/folder.svg
diff --git a/instrumentserver/resource/icons/load.svg b/src/instrumentserver/resource/icons/load.svg
similarity index 100%
rename from instrumentserver/resource/icons/load.svg
rename to src/instrumentserver/resource/icons/load.svg
diff --git a/instrumentserver/resource/icons/plus-square.svg b/src/instrumentserver/resource/icons/plus-square.svg
similarity index 100%
rename from instrumentserver/resource/icons/plus-square.svg
rename to src/instrumentserver/resource/icons/plus-square.svg
diff --git a/instrumentserver/resource/icons/python.svg b/src/instrumentserver/resource/icons/python.svg
similarity index 100%
rename from instrumentserver/resource/icons/python.svg
rename to src/instrumentserver/resource/icons/python.svg
diff --git a/instrumentserver/resource/icons/refresh.svg b/src/instrumentserver/resource/icons/refresh.svg
similarity index 100%
rename from instrumentserver/resource/icons/refresh.svg
rename to src/instrumentserver/resource/icons/refresh.svg
diff --git a/instrumentserver/resource/icons/save.svg b/src/instrumentserver/resource/icons/save.svg
similarity index 100%
rename from instrumentserver/resource/icons/save.svg
rename to src/instrumentserver/resource/icons/save.svg
diff --git a/instrumentserver/resource/icons/server_app_icon.svg b/src/instrumentserver/resource/icons/server_app_icon.svg
similarity index 100%
rename from instrumentserver/resource/icons/server_app_icon.svg
rename to src/instrumentserver/resource/icons/server_app_icon.svg
diff --git a/instrumentserver/resource/icons/set.svg b/src/instrumentserver/resource/icons/set.svg
similarity index 100%
rename from instrumentserver/resource/icons/set.svg
rename to src/instrumentserver/resource/icons/set.svg
diff --git a/instrumentserver/resource/icons/star-crossed.svg b/src/instrumentserver/resource/icons/star-crossed.svg
similarity index 100%
rename from instrumentserver/resource/icons/star-crossed.svg
rename to src/instrumentserver/resource/icons/star-crossed.svg
diff --git a/instrumentserver/resource/icons/star.svg b/src/instrumentserver/resource/icons/star.svg
similarity index 100%
rename from instrumentserver/resource/icons/star.svg
rename to src/instrumentserver/resource/icons/star.svg
diff --git a/instrumentserver/resource/icons/trash-crossed.svg b/src/instrumentserver/resource/icons/trash-crossed.svg
similarity index 100%
rename from instrumentserver/resource/icons/trash-crossed.svg
rename to src/instrumentserver/resource/icons/trash-crossed.svg
diff --git a/instrumentserver/resource/icons/trash.svg b/src/instrumentserver/resource/icons/trash.svg
similarity index 100%
rename from instrumentserver/resource/icons/trash.svg
rename to src/instrumentserver/resource/icons/trash.svg
diff --git a/instrumentserver/resource/style.css b/src/instrumentserver/resource/style.css
similarity index 100%
rename from instrumentserver/resource/style.css
rename to src/instrumentserver/resource/style.css
diff --git a/instrumentserver/schemas/instruction_dict.json b/src/instrumentserver/schemas/instruction_dict.json
similarity index 100%
rename from instrumentserver/schemas/instruction_dict.json
rename to src/instrumentserver/schemas/instruction_dict.json
diff --git a/instrumentserver/schemas/parameters.json b/src/instrumentserver/schemas/parameters.json
similarity index 100%
rename from instrumentserver/schemas/parameters.json
rename to src/instrumentserver/schemas/parameters.json
diff --git a/instrumentserver/serialize.py b/src/instrumentserver/serialize.py
similarity index 74%
rename from instrumentserver/serialize.py
rename to src/instrumentserver/serialize.py
index 0f7bc98..59c4554 100644
--- a/instrumentserver/serialize.py
+++ b/src/instrumentserver/serialize.py
@@ -68,28 +68,27 @@
import json
import logging
-import os
-from typing import Dict, List, Any, Union
-from dataclasses import fields, dataclass
+from typing import Any, Dict, List, Union
-from jsonschema import validate
import pandas as pd
-from qcodes import Instrument, Station, Parameter
+from jsonschema import validate
+from qcodes import Parameter, Station
from qcodes.instrument.base import InstrumentBase
from . import PARAMS_SCHEMA_PATH
-
logger = logging.getLogger(__name__)
SerializableType = Union[Station, List[Union[InstrumentBase, Parameter]]]
-def toParamDict(input: SerializableType,
- get: bool = False,
- includeMeta: List[str] = [],
- excludeParameters: List[str] = [],
- simpleFormat: bool = True) -> Dict:
+def toParamDict(
+ input: SerializableType,
+ get: bool = False,
+ includeMeta: List[str] = [],
+ excludeParameters: List[str] = [],
+ simpleFormat: bool = True,
+) -> Dict:
"""Create a dictionary that holds parameter values, and optionally additional
information about them.
@@ -113,33 +112,43 @@ def toParamDict(input: SerializableType,
snap = input.get_snapshot()
else:
snap = input.snapshot()
- input = [getattr(input, k) for k in snap['instruments'].keys()] \
- + [getattr(input, k) for k in snap['parameters'].keys()] \
- + [getattr(input, k) for k in snap['components'].keys()]
+ input = (
+ [getattr(input, k) for k in snap["instruments"].keys()]
+ + [getattr(input, k) for k in snap["parameters"].keys()]
+ + [getattr(input, k) for k in snap["components"].keys()]
+ )
ret = {}
for obj in input:
if isinstance(obj, InstrumentBase):
- ret.update(_singleInstrumentParametersToJson(
- obj, get=get, addPrefix=f"{obj.name}.",
- includeMeta=includeMeta,
- simpleFormat=simpleFormat,
- excludeParameters=excludeParameters))
+ ret.update(
+ _singleInstrumentParametersToJson(
+ obj,
+ get=get,
+ addPrefix=f"{obj.name}.",
+ includeMeta=includeMeta,
+ simpleFormat=simpleFormat,
+ excludeParameters=excludeParameters,
+ )
+ )
elif isinstance(obj, Parameter):
- ret.update(_singleParameterToJson(
- obj, get=get, simpleFormat=simpleFormat,
- includeMeta=includeMeta))
+ ret.update(
+ _singleParameterToJson(
+ obj, get=get, simpleFormat=simpleFormat, includeMeta=includeMeta
+ )
+ )
else:
- raise ValueError(f"Invalid object: {obj}. Can only process "
- f"Station, Instrument, and Parameter.")
+ raise ValueError(
+ f"Invalid object: {obj}. Can only process "
+ f"Station, Instrument, and Parameter."
+ )
return ret
-def fromParamDict(paramDict: Dict[str, Any],
- target: SerializableType) -> None:
+def fromParamDict(paramDict: Dict[str, Any], target: SerializableType) -> None:
"""Load parameter values from JSON.
:param paramDict: The parameter dictionary in a valid JSON format (may be
@@ -151,7 +160,7 @@ def fromParamDict(paramDict: Dict[str, Any],
simple = isSimpleFormat(paramDict)
for k in sorted(paramDict.keys()):
- paramAsList = k.split('.')
+ paramAsList = k.split(".")
parent = _getObjectByName(paramAsList[0], src=target)
if parent is None:
@@ -167,9 +176,9 @@ def fromParamDict(paramDict: Dict[str, Any],
if simple:
value = paramDict[k]
else:
- value = paramDict[k]['value']
+ value = paramDict[k]["value"]
- if hasattr(param, 'set'):
+ if hasattr(param, "set"):
logger.info(f"[{k}] set to: {value}")
try:
param.set(value)
@@ -178,22 +187,24 @@ def fromParamDict(paramDict: Dict[str, Any],
else:
logger.info(f"[{k}] does not support setting, ignore.")
+
# Tools
-def isSimpleFormat(paramDict: Dict[str, Any]):
+
+def isSimpleFormat(paramDict: Dict[str, Any]) -> bool:
"""Checks if the supplied paramDict is in the simplified format.
We identify the simple format by the fact that otherwise **all** item values
are a dictionary with a least the key `value` in it.
"""
for k, v in paramDict.items():
- if not isinstance(v, dict) or not "value" in v:
+ if not isinstance(v, dict) or "value" not in v:
return True
return False
-def validateParamDict(params: Dict[str, Any]):
+def validateParamDict(params: Dict[str, Any]) -> None:
if isSimpleFormat(params):
return
@@ -205,19 +216,22 @@ def validateParamDict(params: Dict[str, Any]):
raise
-def toDataFrame(input: SerializableType):
+def toDataFrame(input: SerializableType) -> "pd.DataFrame":
"""Make a pandas data frame from the parameters. Mainly useful for
printing overviews in notebooks."""
- params = toParamDict(input, includeMeta=['unit', 'vals'])
+ params = toParamDict(input, includeMeta=["unit", "vals"])
return pd.DataFrame(params).T.sort_index()
# private tool functions
-def _singleParameterToJson(parameter: Parameter,
- get: bool = False,
- includeMeta: List[str] = [],
- simpleFormat: bool = True) -> Dict:
+
+def _singleParameterToJson(
+ parameter: Parameter,
+ get: bool = False,
+ includeMeta: List[str] = [],
+ simpleFormat: bool = True,
+) -> Dict:
"""Create a JSON representation of a parameter."""
ret: dict[str, Any] = {parameter.name: None}
@@ -226,21 +240,23 @@ def _singleParameterToJson(parameter: Parameter,
else:
snap = parameter.snapshot(update=get)
if len(includeMeta) == 0 and simpleFormat:
- ret[parameter.name] = snap.get('value', None)
+ ret[parameter.name] = snap.get("value", None)
else:
ret[parameter.name] = dict()
- for k in ['value'] + includeMeta:
+ for k in ["value"] + includeMeta:
ret[parameter.name][k] = snap.get(k, None)
return ret
-def _singleInstrumentParametersToJson(instrument: InstrumentBase,
- get: bool = False,
- addPrefix: str = '',
- includeMeta: List[str] = [],
- excludeParameters: List[str] = [],
- simpleFormat: bool = True) -> Dict:
+def _singleInstrumentParametersToJson(
+ instrument: InstrumentBase,
+ get: bool = False,
+ addPrefix: str = "",
+ includeMeta: List[str] = [],
+ excludeParameters: List[str] = [],
+ simpleFormat: bool = True,
+) -> Dict:
"""Create a dictionary that holds the parameters of an instrument."""
if "IDN" not in excludeParameters:
@@ -254,20 +270,25 @@ def _singleInstrumentParametersToJson(instrument: InstrumentBase,
for name, param in instrument.parameters.items():
if (name not in excludeParameters) and (not param.snapshot_exclude):
if len(includeMeta) == 0 and simpleFormat:
- ret[addPrefix + name] = snap['parameters'][name].get('value', None)
+ ret[addPrefix + name] = snap["parameters"][name].get("value", None)
else:
ret[addPrefix + name] = dict()
- for k, v in snap['parameters'][name].items():
- if k in (['value'] + includeMeta):
+ for k, v in snap["parameters"][name].items():
+ if k in (["value"] + includeMeta):
ret[addPrefix + name][k] = v
else:
logger.debug(f"excluded: {addPrefix + name}")
for name, submod in instrument.submodules.items():
- ret.update(_singleInstrumentParametersToJson(
- # FIXME: Fix this mypy ignore
- submod, get=get, addPrefix=f"{addPrefix + name}.", # type: ignore[arg-type]
- simpleFormat=simpleFormat, includeMeta=includeMeta))
+ ret.update(
+ _singleInstrumentParametersToJson(
+ submod, # type: ignore[arg-type]
+ get=get,
+ addPrefix=f"{addPrefix + name}.",
+ simpleFormat=simpleFormat,
+ includeMeta=includeMeta,
+ )
+ )
return ret
@@ -289,8 +310,7 @@ def _getParamFromList(parent: Any, childrenList: List[str]) -> Parameter:
return _getParamFromList(nextObj, childrenList[1:])
-def _getObjectByName(name: str,
- src: SerializableType):
+def _getObjectByName(name: str, src: SerializableType) -> Any:
"""Get an object from a container by specifying its name."""
if isinstance(src, Station):
@@ -304,4 +324,3 @@ def _getObjectByName(name: str,
if elt.name == name:
return elt
return None
-
diff --git a/instrumentserver/server/__init__.py b/src/instrumentserver/server/__init__.py
similarity index 100%
rename from instrumentserver/server/__init__.py
rename to src/instrumentserver/server/__init__.py
diff --git a/instrumentserver/server/application.py b/src/instrumentserver/server/application.py
similarity index 64%
rename from instrumentserver/server/application.py
rename to src/instrumentserver/server/application.py
index cf3a8d2..83fcc0a 100644
--- a/instrumentserver/server/application.py
+++ b/src/instrumentserver/server/application.py
@@ -2,22 +2,19 @@
import importlib
import logging
import os
-import time
import sys
-from typing import Union, Optional, Any, Dict
+import time
+from typing import Any, Dict, List, Optional, Tuple, Union, cast
from instrumentserver.client import QtClient
from instrumentserver.log import LogLevels, LogWidget, log
-from .core import (
- StationServer,
- InstrumentModuleBluePrint, ParameterBluePrint
-)
-from .. import QtCore, QtWidgets, QtGui, Client, getInstrumentserverPath
-from ..gui.misc import DetachableTabWidget, BaseDialog
-from ..gui.parameters import AnyInputForMethod
-from ..gui.instruments import GenericInstrument
+from .. import Client, QtCore, QtGui, QtWidgets, getInstrumentserverPath
from ..config import GUIFIELD
+from ..gui.instruments import GenericInstrument
+from ..gui.misc import BaseDialog, DetachableTabWidget
+from ..gui.parameters import AnyInputForMethod
+from .core import InstrumentModuleBluePrint, ParameterBluePrint, StationServer
logger = logging.getLogger(__name__)
@@ -35,7 +32,7 @@
class StationList(QtWidgets.QTreeWidget):
"""A widget that displays all objects in a qcodes station."""
- cols = ['Name', 'Type']
+ cols = ["Name", "Type"]
#: Signal(str) -- emitted when a parameter or Instrument is selected.
#: Argument is the name of the selected instrument
@@ -45,7 +42,7 @@ class StationList(QtWidgets.QTreeWidget):
#: Argument is the name of the instrument that should be closed
closeRequested = QtCore.Signal(str)
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super().__init__(parent)
self.setColumnCount(len(self.cols))
@@ -54,32 +51,41 @@ def __init__(self, parent=None):
self.clear()
self.deleteAction = QtWidgets.QAction("Close Instrument")
- self.deleteAction.setShortcuts(['Del', 'backspace'])
+ self.deleteAction.setShortcuts(["Del", "backspace"])
# you need to add the action to the widget so that it can detect the shortcut
self.addAction(self.deleteAction)
self.contextMenu = QtWidgets.QMenu(self)
self.contextMenu.addAction(self.deleteAction)
- self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
- self.customContextMenuRequested.connect(lambda x: self.contextMenu.exec_(self.mapToGlobal(x)))
+ self.customContextMenuRequested.connect(
+ lambda x: self.contextMenu.exec_(self.mapToGlobal(x)) # type: ignore[arg-type]
+ )
self.deleteAction.triggered.connect(self.onDeleteAction)
self.itemSelectionChanged.connect(self._processSelection)
- def addInstrument(self, bp: InstrumentModuleBluePrint):
+ def addInstrument(self, bp: InstrumentModuleBluePrint) -> None:
lst = [bp.name, f"{bp.instrument_module_class.split('.')[-1]}"]
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(lst))
self.resizeColumnToContents(0)
- def removeObject(self, name: str):
- items = self.findItems(name, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
+ def removeObject(self, name: str) -> None:
+ items = self.findItems(
+ name,
+ cast(
+ "QtCore.Qt.MatchFlags",
+ QtCore.Qt.MatchFlag.MatchExactly | QtCore.Qt.MatchFlag.MatchRecursive,
+ ),
+ 0,
+ )
if len(items) > 0:
item = items[0]
idx = self.indexOfTopLevelItem(item)
self.takeTopLevelItem(idx)
del item
- def _processSelection(self):
+ def _processSelection(self) -> None:
items = self.selectedItems()
if len(items) == 0:
return
@@ -87,15 +93,19 @@ def _processSelection(self):
self.componentSelected.emit(item.text(0))
@QtCore.Slot()
- def onDeleteAction(self):
+ def onDeleteAction(self) -> None:
# need to check if widget has focus because of the keyboard shortcuts
if self.hasFocus():
items = self.selectedItems()
for item in items:
msgBox = QtWidgets.QMessageBox()
msgBox.setWindowTitle("Confirm Close Instrument")
- msgBox.setText(f'Are you sure you want to close instrument "{item.text(0)}"')
- msgBox.setStandardButtons(QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No)
+ msgBox.setText(
+ f'Are you sure you want to close instrument "{item.text(0)}"'
+ )
+ msgBox.setStandardButtons(
+ QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
+ )
msgBox.setDefaultButton(QtWidgets.QMessageBox.No)
ret = msgBox.exec()
if ret == QtWidgets.QMessageBox.Yes:
@@ -103,56 +113,56 @@ def onDeleteAction(self):
class StationObjectInfo(QtWidgets.QTextEdit):
-
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super().__init__(parent)
self.setReadOnly(True)
@QtCore.Slot(object)
- def setObject(self, bp: InstrumentModuleBluePrint):
+ def setObject(self, bp: InstrumentModuleBluePrint) -> None:
self.setHtml(bluePrintToHtml(bp))
class ServerStatus(QtWidgets.QWidget):
"""A widget that shows the status of the instrument server."""
- def __init__(self, parent=None):
+ def __init__(self, parent: Optional[QtWidgets.QWidget] = None) -> None:
super().__init__(parent)
- self.layout = QtWidgets.QVBoxLayout(self)
+ self.layout = QtWidgets.QVBoxLayout(self) # type: ignore[assignment,method-assign]
# At the top: a status label, and a button for emitting a test message
self.addressLabel = QtWidgets.QLabel()
- self.testButton = QtWidgets.QPushButton('Send test message')
+ self.testButton = QtWidgets.QPushButton("Send test message")
self.statusLayout = QtWidgets.QHBoxLayout()
self.statusLayout.addWidget(self.addressLabel, 1)
self.statusLayout.addWidget(self.testButton, 0)
self.testButton.setSizePolicy(
- QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed,
- QtWidgets.QSizePolicy.Minimum)
+ QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum
+ )
)
- self.layout.addLayout(self.statusLayout)
+ self.layout.addLayout(self.statusLayout) # type: ignore[attr-defined]
# next row: a window for displaying the incoming messages.
- self.layout.addWidget(QtWidgets.QLabel('Messages:'))
+ self.layout.addWidget(QtWidgets.QLabel("Messages:")) # type: ignore[attr-defined]
self.messages = QtWidgets.QTextEdit()
self.messages.setReadOnly(True)
- self.layout.addWidget(self.messages)
+ self.layout.addWidget(self.messages) # type: ignore[attr-defined]
@QtCore.Slot(str)
- def setListeningAddress(self, addr: str):
+ def setListeningAddress(self, addr: str) -> None:
self.addressLabel.setText(f"Listening on: {addr}")
@QtCore.Slot(str, str)
- def addMessageAndReply(self, message: str, reply: str):
+ def addMessageAndReply(self, message: str, reply: str) -> None:
tstr = time.strftime("%Y-%m-%d %H:%M:%S")
- self.messages.setTextColor(QtGui.QColor('black'))
+ self.messages.setTextColor(QtGui.QColor("black"))
self.messages.append(f"[{tstr}]")
- self.messages.setTextColor(QtGui.QColor('blue'))
+ self.messages.setTextColor(QtGui.QColor("blue"))
self.messages.append(f"Server received: {message}")
- self.messages.setTextColor(QtGui.QColor('green'))
+ self.messages.setTextColor(QtGui.QColor("green"))
self.messages.append(f"Server replied: {reply}")
@@ -165,13 +175,23 @@ class CreateInstrumentDialog(BaseDialog):
:param insName: Optional, The name of the instrument
:param kwargsStr: Optional, String with te args and kwargs separated by commas.
"""
+
createInstrument = QtCore.Signal(str, str, tuple)
- def __init__(self, insType: Optional[str] = None, insName: Optional[str] = None, kwargsStr: Optional[str] = None,
- parent=None, flags=(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowCloseButtonHint),):
+ def __init__(
+ self,
+ insType: Optional[str] = None,
+ insName: Optional[str] = None,
+ kwargsStr: Optional[str] = None,
+ parent: Optional[QtWidgets.QWidget] = None,
+ flags: Any = (
+ QtCore.Qt.WindowType.CustomizeWindowHint
+ | QtCore.Qt.WindowType.WindowCloseButtonHint
+ ),
+ ) -> None:
super().__init__(parent, flags)
- tittleText = 'Create New Instrument'
+ tittleText = "Create New Instrument"
self.setWindowTitle(tittleText)
layout = QtWidgets.QVBoxLayout(self)
@@ -187,23 +207,25 @@ def __init__(self, insType: Optional[str] = None, insName: Optional[str] = None,
self.argsEdit.input.setText(kwargsStr)
self.argsEdit.doEval.hide()
- formLayout.addRow(QtWidgets.QLabel('Instrument Type:'), self.typeEdit)
- formLayout.addRow(QtWidgets.QLabel('Instrument Name:'), self.nameEdit)
- formLayout.addRow(QtWidgets.QLabel('Args and Kwargs:'), self.argsEdit)
+ formLayout.addRow(QtWidgets.QLabel("Instrument Type:"), self.typeEdit)
+ formLayout.addRow(QtWidgets.QLabel("Instrument Name:"), self.nameEdit)
+ formLayout.addRow(QtWidgets.QLabel("Args and Kwargs:"), self.argsEdit)
layout.addLayout(formLayout)
self.acceptButton = QtWidgets.QPushButton("Create")
self.acceptButton.setDefault(True)
- buttonSizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum)
+ buttonSizePolicy = QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Maximum
+ )
self.acceptButton.setSizePolicy(buttonSizePolicy)
layout.addWidget(self.acceptButton)
- layout.setAlignment(self.acceptButton, QtCore.Qt.AlignCenter)
+ layout.setAlignment(self.acceptButton, QtCore.Qt.AlignmentFlag.AlignCenter)
self.acceptButton.clicked.connect(self.onAcceptButton)
@QtCore.Slot()
- def onAcceptButton(self):
+ def onAcceptButton(self) -> None:
insType = self.typeEdit.text()
insName = self.nameEdit.text()
# Args is not a normal line edit, value evaluates the args and kwargs already
@@ -212,7 +234,7 @@ def onAcceptButton(self):
@QtCore.Slot(str)
-def onExceptionDialog(exception: str):
+def onExceptionDialog(exception: str) -> None:
"""
Opens a dialog displaying an exception.
@@ -222,17 +244,17 @@ def onExceptionDialog(exception: str):
dialog.setWindowTitle("Instrument Creation Error")
layout = QtWidgets.QVBoxLayout(dialog)
- exceptionRaisedLabel = QtWidgets.QLabel(f"Exception Raised:")
+ exceptionRaisedLabel = QtWidgets.QLabel("Exception Raised:")
exceptionLabel = QtWidgets.QLabel(exception)
accept = QtWidgets.QPushButton("Accept")
layout.addWidget(exceptionRaisedLabel)
- layout.setAlignment(exceptionRaisedLabel, QtCore.Qt.AlignCenter)
+ layout.setAlignment(exceptionRaisedLabel, QtCore.Qt.AlignmentFlag.AlignCenter)
layout.addWidget(exceptionLabel)
- layout.setAlignment(exceptionLabel, QtCore.Qt.AlignCenter)
+ layout.setAlignment(exceptionLabel, QtCore.Qt.AlignmentFlag.AlignCenter)
layout.addWidget(accept)
- layout.setAlignment(accept, QtCore.Qt.AlignCenter)
+ layout.setAlignment(accept, QtCore.Qt.AlignmentFlag.AlignCenter)
accept.clicked.connect(dialog.accept)
dialog.exec_()
@@ -242,7 +264,16 @@ class PossibleInstrumentDisplayItem(QtWidgets.QTreeWidgetItem):
"""
Items used in the PossibleInstrumentDisplay. Need to have a custom one to store extra info.
"""
- def __init__(self, text, fullInsType, configName=None, lineEdit=None, *args, **kwargs):
+
+ def __init__(
+ self,
+ text: List[str],
+ fullInsType: str,
+ configName: Optional[str] = None,
+ lineEdit: Optional[QtWidgets.QLineEdit] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> None:
super().__init__(text, *args, **kwargs)
self.configName = configName
self.lineEdit = lineEdit
@@ -274,14 +305,16 @@ class PossibleInstrumentsDisplay(QtWidgets.QTreeWidget):
cols = ["Instrument Type & Preset", "Instrument Name", "Create Instrument"]
- def __init__(self, guiConfig: Optional[dict] = None, *args):
+ def __init__(self, guiConfig: Optional[dict] = None, *args: Any) -> None:
super().__init__(*args)
self.setColumnCount(len(self.cols))
self.setHeaderLabels(self.cols)
- self.basedInstrumentAction = QtWidgets.QAction(f'Create instrument based on this')
- self.basedInstrumentAction.setShortcut('N')
+ self.basedInstrumentAction = QtWidgets.QAction(
+ "Create instrument based on this"
+ )
+ self.basedInstrumentAction.setShortcut("N")
# you need to add the action to the widget so that it can detect the shortcut
self.addAction(self.basedInstrumentAction)
@@ -289,14 +322,18 @@ def __init__(self, guiConfig: Optional[dict] = None, *args):
self.deletePossibleInstrumentAction = QtWidgets.QAction("Delete")
self.contextMenu = QtWidgets.QMenu(self)
- self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.contextMenu.addAction(self.basedInstrumentAction)
self.contextMenu.addSeparator()
self.contextMenu.addAction(self.deletePossibleInstrumentAction)
- self.customContextMenuRequested.connect(lambda x: self.contextMenu.exec_(self.mapToGlobal(x)))
+ self.customContextMenuRequested.connect(
+ lambda x: self.contextMenu.exec_(self.mapToGlobal(x)) # type: ignore[arg-type]
+ )
self.basedInstrumentAction.triggered.connect(self.onBasedInstrumentAction)
- self.deletePossibleInstrumentAction.triggered.connect(self.onRemoveInstrumentFromTree)
+ self.deletePossibleInstrumentAction.triggered.connect(
+ self.onRemoveInstrumentFromTree
+ )
self.config = {}
if guiConfig is not None:
@@ -305,26 +342,42 @@ def __init__(self, guiConfig: Optional[dict] = None, *args):
self.expandAll()
- def loadConfig(self, config: dict):
+ def loadConfig(self, config: dict) -> None:
for key, value in config.items():
# In the config, the name of the instrument and the config name are the same.
- self.addInstrumentToTree(value['type'], key, key)
+ self.addInstrumentToTree(value["type"], key, key)
self.resizeColumnToContents(0)
self.resizeColumnToContents(1)
- def addInstrumentToTree(self, fullInsType: str = 'InstrumentType', insName: str = 'MyInstrument', configName: Optional[str]=None):
+ def addInstrumentToTree(
+ self,
+ fullInsType: str = "InstrumentType",
+ insName: str = "MyInstrument",
+ configName: Optional[str] = None,
+ ) -> None:
"""
Each type is grouped together under a parent item of that type. If that parent item does not exist yet it
creates it
"""
- insType = fullInsType.split('.')[-1]
- items = self.findItems(insType, QtCore.Qt.MatchExactly | QtCore.Qt.MatchExactly, 0)
+ insType = fullInsType.split(".")[-1]
+ items = self.findItems(
+ insType,
+ cast(
+ "QtCore.Qt.MatchFlags",
+ QtCore.Qt.MatchFlag.MatchExactly | QtCore.Qt.MatchFlag.MatchExactly,
+ ),
+ 0,
+ )
# Only add the instrument to the tree if there are no other instruments of the same type already
if len(items) == 0:
parent: PossibleInstrumentDisplayItem | QtWidgets.QTreeWidgetItem = (
- PossibleInstrumentDisplayItem(text=[insType, '', ''], fullInsType=fullInsType,))
+ PossibleInstrumentDisplayItem(
+ text=[insType, "", ""],
+ fullInsType=fullInsType,
+ )
+ )
self.addTopLevelItem(parent)
self.expand(self.indexFromItem(parent, 0))
else:
@@ -335,28 +388,42 @@ def addInstrumentToTree(self, fullInsType: str = 'InstrumentType', insName: str
createButton = QtWidgets.QPushButton("Create")
- lst = [configName, insName, 'create']
+ lst: list[str] = [configName or "", insName, "create"]
lineEdit = QtWidgets.QLineEdit()
lineEdit.returnPressed.connect(lambda: createButton.clicked.emit())
lineEdit.setText(insName)
- item = PossibleInstrumentDisplayItem(lst, fullInsType=fullInsType, configName=configName, lineEdit=lineEdit)
+ item = PossibleInstrumentDisplayItem(
+ lst,
+ fullInsType=fullInsType,
+ configName=configName,
+ lineEdit=lineEdit,
+ )
parent.addChild(item)
self.setItemWidget(item, 1, lineEdit)
self.setItemWidget(item, 2, createButton)
- createButton.clicked.connect(lambda: self.createButtonPressed.emit(configName, fullInsType, lineEdit.text()))
+ createButton.clicked.connect(
+ lambda: self.createButtonPressed.emit(
+ configName, fullInsType, lineEdit.text()
+ )
+ )
- def onBasedInstrumentAction(self):
+ def onBasedInstrumentAction(self) -> None:
items = self.selectedItems()
- for item in items:
+ for raw_item in items:
+ item = cast(PossibleInstrumentDisplayItem, raw_item)
insName = None
if item.lineEdit is not None:
insName = item.lineEdit.text()
- self.basedInstrumentRequested.emit(item.configName, item.fullInsType, insName)
+ self.basedInstrumentRequested.emit(
+ item.configName,
+ item.fullInsType,
+ insName,
+ )
@QtCore.Slot()
- def onRemoveInstrumentFromTree(self):
+ def onRemoveInstrumentFromTree(self) -> None:
"""
Removes both the potential instrument from the widget and the presets from the config dictionary.
"""
@@ -364,16 +431,16 @@ def onRemoveInstrumentFromTree(self):
for item in items:
if item.childCount() == 0:
parent = item.parent()
- if item.configName is not None and item.configName in self.config:
- del self.config[item.configName]
- parent.removeChild(item)
- if parent.childCount() == 0:
+ if item.configName is not None and item.configName in self.config: # type: ignore[attr-defined]
+ del self.config[item.configName] # type: ignore[attr-defined]
+ parent.removeChild(item) # type: ignore[union-attr]
+ if parent.childCount() == 0: # type: ignore[union-attr]
self.takeTopLevelItem((self.indexOfTopLevelItem(parent)))
else:
for i in range(item.childCount()):
child = item.child(i)
- if child.configName in self.config:
- del self.config[child.configName]
+ if child.configName in self.config: # type: ignore[union-attr]
+ del self.config[child.configName] # type: ignore[union-attr]
self.takeTopLevelItem(self.indexOfTopLevelItem(item))
@@ -389,6 +456,7 @@ class InstrumentsCreator(QtWidgets.QWidget):
configs get updated too.
:param stationServer: The station server. We just need to connect to some of the signals that it sends
"""
+
#: Signal()-- emitted when the InstrumentCreator creates a new signal. Used to close the creation instrument widget.
newInstrumentCreated = QtCore.Signal()
@@ -396,7 +464,7 @@ class InstrumentsCreator(QtWidgets.QWidget):
#: Arguments -- The str message of the error/reason as to why it could not create the instrument
newInstrumentFailed = QtCore.Signal(object)
- def __init__(self, cli: Client, guiConfig: dict, *args, **kwargs):
+ def __init__(self, cli: Client, guiConfig: dict, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.guiConfig = guiConfig
@@ -410,38 +478,55 @@ def __init__(self, cli: Client, guiConfig: dict, *args, **kwargs):
layout.addWidget(self.possibleInstrumentDisplay)
layout.addWidget(self.createNewButton, 0)
- self.createNewButton.clicked.connect(lambda: self.onCreateNewInstrumentClicked(None, None, None))
- self.possibleInstrumentDisplay.createButtonPressed.connect(self.onPossibleInstrumentDisplayClicked)
- self.possibleInstrumentDisplay.basedInstrumentRequested.connect(self.onCreateNewInstrumentClicked)
+ self.createNewButton.clicked.connect(
+ lambda: self.onCreateNewInstrumentClicked(None, None, None)
+ )
+ self.possibleInstrumentDisplay.createButtonPressed.connect(
+ self.onPossibleInstrumentDisplayClicked
+ )
+ self.possibleInstrumentDisplay.basedInstrumentRequested.connect(
+ self.onCreateNewInstrumentClicked
+ )
self.newInstrumentFailed.connect(onExceptionDialog)
- self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum))
+ self.setSizePolicy(
+ QtWidgets.QSizePolicy(
+ QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Minimum
+ )
+ )
@QtCore.Slot(str, str, str)
- def onCreateNewInstrumentClicked(self, configName: Optional[str] = None,
- insType: Optional[str] = None,
- insName: Optional[str] = None):
+ def onCreateNewInstrumentClicked(
+ self,
+ configName: Optional[str] = None,
+ insType: Optional[str] = None,
+ insName: Optional[str] = None,
+ ) -> None:
# go through all the possible arguments in the config and write them in kwarg form
kwargsStr = None
if configName is not None and configName in self.guiConfig:
conf = self.guiConfig[configName]
- kwargsStr = ''
- if 'address' in conf:
- kwargsStr = kwargsStr + 'address=' + str(conf['address'])
- if 'init' in conf:
- for k, v in conf['init'].items():
- kwargsStr = kwargsStr + ',' + str(k) + '=' + str(v)
+ kwargsStr = ""
+ if "address" in conf:
+ kwargsStr = kwargsStr + "address=" + str(conf["address"])
+ if "init" in conf:
+ for k, v in conf["init"].items():
+ kwargsStr = kwargsStr + "," + str(k) + "=" + str(v)
# If there is no address argument the first item will be a comma making the creation crash
- if len(kwargsStr) > 0 and kwargsStr[0] == ',':
+ if len(kwargsStr) > 0 and kwargsStr[0] == ",":
kwargsStr = kwargsStr[1:]
- dialog = CreateInstrumentDialog(insType=insType, insName=insName, kwargsStr=kwargsStr, parent=self)
+ dialog = CreateInstrumentDialog(
+ insType=insType, insName=insName, kwargsStr=kwargsStr, parent=self
+ )
dialog.createInstrument.connect(self.onDialogNewInstrument)
self.newInstrumentCreated.connect(dialog.accept)
dialog.exec_()
@QtCore.Slot(str, str, tuple)
- def onDialogNewInstrument(self, insType, insName, argsKwargs):
+ def onDialogNewInstrument(
+ self, insType: str, insName: str, argsKwargs: Tuple[Any, Any]
+ ) -> None:
args, kwargs = argsKwargs
if args is None:
@@ -451,35 +536,52 @@ def onDialogNewInstrument(self, insType, insName, argsKwargs):
self.createNewInstrument(insType, insName, *args, **kwargs)
@QtCore.Slot(str, str, str)
- def onPossibleInstrumentDisplayClicked(self, configName, insType, insName):
+ def onPossibleInstrumentDisplayClicked(
+ self, configName: str, insType: str, insName: str
+ ) -> None:
"""
Creates new instrument based on a possible instrument. Only creates it if it can find the configName in the
config.
"""
if configName in self.guiConfig:
-
if insName in self.cli.list_instruments():
- self.newInstrumentFailed.emit(f'Instrument with name "{insName}" already exists')
+ self.newInstrumentFailed.emit(
+ f'Instrument with name "{insName}" already exists'
+ )
return
# In the qcodes station config, the call the kwargs of the instrument 'init'
- kwargs = dict() if 'init' not in self.guiConfig[configName] else dict(self.guiConfig[configName]['init'])
- args = [] if 'args' not in self.guiConfig[configName] else self.guiConfig[configName]['args']
-
- if 'address' in self.guiConfig[configName]:
- kwargs['address'] = self.guiConfig[configName]['address']
+ kwargs = (
+ dict()
+ if "init" not in self.guiConfig[configName]
+ else dict(self.guiConfig[configName]["init"])
+ )
+ args = (
+ []
+ if "args" not in self.guiConfig[configName]
+ else self.guiConfig[configName]["args"]
+ )
+
+ if "address" in self.guiConfig[configName]:
+ kwargs["address"] = self.guiConfig[configName]["address"]
self.createNewInstrument(insType, insName, *args, **kwargs)
else:
- self.newInstrumentFailed.emit("you cannot create instruments that are not in the config from here yet")
+ self.newInstrumentFailed.emit(
+ "you cannot create instruments that are not in the config from here yet"
+ )
- def createNewInstrument(self, insType, insName, *args, **kwargs):
+ def createNewInstrument(
+ self, insType: str, insName: str, *args: Any, **kwargs: Any
+ ) -> None:
if insName in self.cli.list_instruments():
self.newInstrumentFailed.emit(f'Instrument "{insName}" already exists.')
return
try:
- self.cli.find_or_create_instrument(name=insName, instrument_class=insType, *args, **kwargs)
+ self.cli.find_or_create_instrument( # type: ignore[misc]
+ name=insName, instrument_class=insType, *args, **kwargs
+ )
self.newInstrumentCreated.emit()
except Exception as e:
self.newInstrumentFailed.emit(str(e))
@@ -490,12 +592,15 @@ class ServerGui(QtWidgets.QMainWindow):
serverPortSet = QtCore.Signal(int)
- def __init__(self, startServer: Optional[bool] = True,
- guiConfig: Optional[dict] = None,
- **serverKwargs: Any):
+ def __init__(
+ self,
+ startServer: Optional[bool] = True,
+ guiConfig: Optional[dict] = None,
+ **serverKwargs: Any,
+ ) -> None:
super().__init__()
- self._paramValuesFile = os.path.abspath(os.path.join('.', 'parameters.json'))
+ self._paramValuesFile = os.path.abspath(os.path.join(".", "parameters.json"))
self._bluePrints: dict[str, InstrumentModuleBluePrint] = {}
self._serverKwargs = serverKwargs
if guiConfig is None:
@@ -508,12 +613,19 @@ def __init__(self, startServer: Optional[bool] = True,
self.instrumentTabsOpen: dict[str, GenericInstrument] = {}
- self.setWindowTitle('Instrument server')
+ self.setWindowTitle("Instrument server")
# Set unique Windows App ID so that this app can have separate taskbar entry than other Qt apps
if sys.platform == "win32":
import ctypes
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("InstrumentServer.Server")
- self.setWindowIcon(QtGui.QIcon(getInstrumentserverPath("resource", "icons") + "/server_app_icon.svg"))
+
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
+ "InstrumentServer.Server"
+ )
+ self.setWindowIcon(
+ QtGui.QIcon(
+ getInstrumentserverPath("resource", "icons") + "/server_app_icon.svg"
+ )
+ )
# A test client, just a simple helper object.
self.client = EmbeddedClient(raise_exceptions=False, timeout=5000)
@@ -532,45 +644,48 @@ def __init__(self, startServer: Optional[bool] = True,
self.stationList.itemDoubleClicked.connect(self.addInstrumentTab)
self.stationList.closeRequested.connect(self.closeInstrument)
- stationWidgets = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
+ stationWidgets = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
stationWidgets.addWidget(self.stationList)
stationWidgets.addWidget(self.stationObjInfo)
stationWidgets.setSizes([300, 500])
- instrumentsWidgets = QtWidgets.QSplitter(QtCore.Qt.Vertical)
+ instrumentsWidgets = QtWidgets.QSplitter(QtCore.Qt.Orientation.Vertical)
instrumentsWidgets.addWidget(stationWidgets)
instrumentsWidgets.addWidget(self.instrumentCreator)
- self.tabs.addUnclosableTab(instrumentsWidgets, 'Station')
- self.tabs.addUnclosableTab(LogWidget(level=logging.INFO), 'Log')
+ self.tabs.addUnclosableTab(instrumentsWidgets, "Station")
+ self.tabs.addUnclosableTab(LogWidget(level=logging.INFO), "Log")
self.serverStatus = ServerStatus()
- self.tabs.addUnclosableTab(self.serverStatus, 'Server')
+ self.tabs.addUnclosableTab(self.serverStatus, "Server")
# Toolbar.
- self.toolBar = self.addToolBar('Tools')
- self.toolBar.setIconSize(QtCore.QSize(16, 16))
+ self.toolBar = self.addToolBar("Tools")
+ self.toolBar.setIconSize(QtCore.QSize(16, 16)) # type: ignore[union-attr]
# Station tools.
- self.toolBar.addWidget(QtWidgets.QLabel('Station:'))
+ self.toolBar.addWidget(QtWidgets.QLabel("Station:")) # type: ignore[union-attr]
self.refreshStationAction = QtWidgets.QAction(
- QtGui.QIcon(":/icons/refresh.svg"), 'Refresh', self)
+ QtGui.QIcon(":/icons/refresh.svg"), "Refresh", self
+ )
self.refreshStationAction.triggered.connect(self.refreshStationComponents)
- self.toolBar.addAction(self.refreshStationAction)
+ self.toolBar.addAction(self.refreshStationAction) # type: ignore[union-attr]
# Parameter tools.
- self.toolBar.addSeparator()
- self.toolBar.addWidget(QtWidgets.QLabel('Params:'))
+ self.toolBar.addSeparator() # type: ignore[union-attr]
+ self.toolBar.addWidget(QtWidgets.QLabel("Params:")) # type: ignore[union-attr]
self.loadParamsAction = QtWidgets.QAction(
- QtGui.QIcon(":/icons/load.svg"), 'Load from file', self)
+ QtGui.QIcon(":/icons/load.svg"), "Load from file", self
+ )
self.loadParamsAction.triggered.connect(self.loadParamsFromFile)
- self.toolBar.addAction(self.loadParamsAction)
+ self.toolBar.addAction(self.loadParamsAction) # type: ignore[union-attr]
self.saveParamsAction = QtWidgets.QAction(
- QtGui.QIcon(":/icons/save.svg"), 'Save to file', self)
+ QtGui.QIcon(":/icons/save.svg"), "Save to file", self
+ )
self.saveParamsAction.triggered.connect(self.saveParamsToFile)
- self.toolBar.addAction(self.saveParamsAction)
+ self.toolBar.addAction(self.saveParamsAction) # type: ignore[union-attr]
self.serverStatus.testButton.clicked.connect(
lambda x: self.client.ask("Ping server.")
@@ -586,46 +701,64 @@ def __init__(self, startServer: Optional[bool] = True,
# printSpaceAction.triggered.connect(lambda x: print("\n \n \n \n"))
# self.toolBar.addAction(printSpaceAction)
- def log(self, message, level=LogLevels.info):
+ def log(self, message: str, level: LogLevels = LogLevels.info) -> None:
log(logger, message, level)
- def closeEvent(self, event):
- if hasattr(self, 'stationServerThread'):
+ def closeEvent(self, event: Optional[QtGui.QCloseEvent]) -> None:
+ for name, widget in list(self.instrumentTabsOpen.items()):
+ try:
+ widget.close()
+ except Exception:
+ pass
+ self.instrumentTabsOpen.clear()
+
+ if (
+ hasattr(self, "stationServerThread")
+ and self.stationServerThread is not None
+ ):
if self.stationServerThread.isRunning():
- self.client.ask(self.stationServer.SAFEWORD)
- event.accept()
+ try:
+ self.client.ask(self.stationServer.SAFEWORD)
+ except Exception:
+ pass
+
+ try:
+ self.client.disconnect()
+ except Exception:
+ pass
+ event.accept() # type: ignore[union-attr]
- def startServer(self):
+ def startServer(self) -> None:
"""Start the instrument server in a separate thread."""
- self.stationServer = StationServer(**self._serverKwargs)
- self.stationServerThread = QtCore.QThread()
- self.stationServer.moveToThread(self.stationServerThread)
- self.stationServerThread.started.connect(self.stationServer.startServer)
- self.stationServer.finished.connect(lambda: self.log('ZMQ server closed.'))
- self.stationServer.finished.connect(self.stationServerThread.quit)
- self.stationServer.finished.connect(self.stationServer.deleteLater)
+ self.stationServer = StationServer(**self._serverKwargs) # type: ignore[assignment]
+ self.stationServerThread = QtCore.QThread() # type: ignore[assignment]
+ self.stationServer.moveToThread(self.stationServerThread) # type: ignore[attr-defined]
+ self.stationServerThread.started.connect(self.stationServer.startServer) # type: ignore[arg-type,attr-defined]
+ self.stationServer.finished.connect(lambda: self.log("ZMQ server closed.")) # type: ignore[attr-defined]
+ self.stationServer.finished.connect(self.stationServerThread.quit) # type: ignore[attr-defined]
+ self.stationServer.finished.connect(self.stationServer.deleteLater) # type: ignore[attr-defined]
# Connecting some additional things for messages.
- self.stationServer.serverStarted.connect(self.serverStatus.setListeningAddress)
- self.stationServer.serverStarted.connect(self.client.start)
- self.stationServer.serverStarted.connect(self.refreshStationComponents)
- self.stationServer.finished.connect(
- lambda: self.log('Server thread finished.', LogLevels.info)
+ self.stationServer.serverStarted.connect(self.serverStatus.setListeningAddress) # type: ignore[attr-defined]
+ self.stationServer.serverStarted.connect(self.client.start) # type: ignore[attr-defined]
+ self.stationServer.serverStarted.connect(self.refreshStationComponents) # type: ignore[attr-defined]
+ self.stationServer.finished.connect( # type: ignore[attr-defined]
+ lambda: self.log("Server thread finished.", LogLevels.info)
)
- self.stationServer.messageReceived.connect(self._messageReceived)
- self.stationServer.instrumentCreated.connect(self.addInstrumentToGui)
- self.stationServer.funcCalled.connect(self.onFuncCalled)
+ self.stationServer.messageReceived.connect(self._messageReceived) # type: ignore[attr-defined]
+ self.stationServer.instrumentCreated.connect(self.addInstrumentToGui) # type: ignore[attr-defined]
+ self.stationServer.funcCalled.connect(self.onFuncCalled) # type: ignore[attr-defined]
- self.stationServerThread.start()
+ self.stationServerThread.start() # type: ignore[attr-defined]
- def getServerIfRunning(self):
- if self.stationServer is not None and self.stationServerThread.isRunning():
+ def getServerIfRunning(self) -> Optional["StationServer"]:
+ if self.stationServer is not None and self.stationServerThread.isRunning(): # type: ignore[union-attr]
return self.stationServer
else:
return None
@QtCore.Slot(str, str)
- def _messageReceived(self, message: str, reply: str):
+ def _messageReceived(self, message: str, reply: str) -> None:
maxLen = 80
messageSummary = message[:maxLen]
if len(message) > maxLen:
@@ -637,7 +770,12 @@ def _messageReceived(self, message: str, reply: str):
self.log(f"Server replied: {reply}", LogLevels.debug)
self.serverStatus.addMessageAndReply(messageSummary, replySummary)
- def addInstrumentToGui(self, instrumentBluePrint: InstrumentModuleBluePrint, insArgs, insKwargs):
+ def addInstrumentToGui(
+ self,
+ instrumentBluePrint: InstrumentModuleBluePrint,
+ insArgs: Any,
+ insKwargs: Any,
+ ) -> None:
"""
Add an instrument to the station list.
@@ -649,19 +787,24 @@ def addInstrumentToGui(self, instrumentBluePrint: InstrumentModuleBluePrint, ins
if instrumentBluePrint.name not in self._guiConfig:
# add the gui config for opening generic GUI's and keep track of the config
if insArgs is None or insArgs == []:
- self._guiConfig[instrumentBluePrint.name] = dict(gui=GUIFIELD,
- type=instrumentBluePrint.instrument_module_class,
- init=insKwargs)
+ self._guiConfig[instrumentBluePrint.name] = dict(
+ gui=GUIFIELD,
+ type=instrumentBluePrint.instrument_module_class,
+ init=insKwargs,
+ )
else:
- self._guiConfig[instrumentBluePrint.name] = dict(gui=GUIFIELD,
- type=instrumentBluePrint.instrument_module_class,
- args=insArgs,
- init=insKwargs)
+ self._guiConfig[instrumentBluePrint.name] = dict(
+ gui=GUIFIELD,
+ type=instrumentBluePrint.instrument_module_class,
+ args=insArgs,
+ init=insKwargs,
+ )
self.instrumentCreator.possibleInstrumentDisplay.addInstrumentToTree(
- instrumentBluePrint.instrument_module_class, instrumentBluePrint.name)
+ instrumentBluePrint.instrument_module_class, instrumentBluePrint.name
+ )
- def removeInstrumentFromGui(self, name: str):
+ def removeInstrumentFromGui(self, name: str) -> None:
"""Remove an instrument from the station list."""
self.stationList.removeObject(name)
del self._bluePrints[name]
@@ -669,48 +812,58 @@ def removeInstrumentFromGui(self, name: str):
self.tabs.removeTab(self.tabs.indexOf(self.instrumentTabsOpen[name]))
del self.instrumentTabsOpen[name]
- def refreshStationComponents(self):
+ def refreshStationComponents(self) -> None:
"""Clear and re-populate the widget holding the station components, using
the objects that are currently registered in the station."""
+ if getattr(self.client, "_closed", False) or not self.client.connected:
+ return
self.stationList.clear()
- for ins in self.client.list_instruments():
+ try:
+ instruments = self.client.list_instruments()
+ except RuntimeError:
+ return
+ if not instruments:
+ return
+ for ins in instruments:
bp = self.client.getBluePrint(ins)
self.stationList.addInstrument(bp)
self._bluePrints[ins] = bp
self.stationList.resizeColumnToContents(0)
- def loadParamsFromFile(self):
+ def loadParamsFromFile(self) -> None:
"""Load the values of all parameters present in the server's params json file
to parameters registered in the station (incl those in instruments)."""
- logger.info(f"Loading parameters from file: "
- f"{os.path.abspath(self._paramValuesFile)}")
+ logger.info(
+ f"Loading parameters from file: {os.path.abspath(self._paramValuesFile)}"
+ )
try:
self.client.paramsFromFile(self._paramValuesFile)
except Exception as e:
logger.error(f"Loading failed. {type(e)}: {e.args}")
- def saveParamsToFile(self):
+ def saveParamsToFile(self) -> None:
"""Save the values of all parameters registered in the station (incl
- those in instruments) to the server's param json file."""
+ those in instruments) to the server's param json file."""
- logger.info(f"Saving parameters to file: "
- f"{os.path.abspath(self._paramValuesFile)}")
+ logger.info(
+ f"Saving parameters to file: {os.path.abspath(self._paramValuesFile)}"
+ )
try:
self.client.paramsToFile(self._paramValuesFile)
except Exception as e:
logger.error(f"Saving failed. {type(e)}: {e.args}")
@QtCore.Slot(str)
- def displayComponentInfo(self, name: Union[str, None]):
+ def displayComponentInfo(self, name: Union[str, None]) -> None:
if name is not None and name in self._bluePrints:
bp = self._bluePrints[name]
else:
bp = None
- self.stationObjInfo.setObject(bp)
+ self.stationObjInfo.setObject(bp) # type: ignore[arg-type]
@QtCore.Slot(QtWidgets.QTreeWidgetItem, int)
- def addInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int):
+ def addInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int) -> None:
"""
Gets called when the user double clicks and item of the instrument list.
Adds a new generic instrument GUI window to the tab bar.
@@ -724,16 +877,18 @@ def addInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int):
# The user might create an instrument that is not in the config file
if name in self._guiConfig:
# import the widget
- moduleName = '.'.join(self._guiConfig[name]['gui']['type'].split('.')[:-1])
- widgetClassName = self._guiConfig[name]['gui']['type'].split('.')[-1]
+ moduleName = ".".join(
+ self._guiConfig[name]["gui"]["type"].split(".")[:-1]
+ )
+ widgetClassName = self._guiConfig[name]["gui"]["type"].split(".")[-1]
module = importlib.import_module(moduleName)
widgetClass = getattr(module, widgetClassName)
# get any kwargs if the config file has any
- if 'kwargs' in self._guiConfig[name]['gui']:
- kwargs = self._guiConfig[name]['gui']['kwargs']
+ if "kwargs" in self._guiConfig[name]["gui"]:
+ kwargs = self._guiConfig[name]["gui"]["kwargs"]
- kwargs["sub_port"] = kwargs.get("sub_port", self.stationServer.port + 1)
+ kwargs["sub_port"] = kwargs.get("sub_port", self.stationServer.port + 1) # type: ignore[union-attr]
insWidget = widgetClass(ins, parent=self, **kwargs)
index = self.tabs.addTab(insWidget, ins.name)
self.instrumentTabsOpen[ins.name] = insWidget
@@ -748,13 +903,13 @@ def onTabDeleted(self, name: str) -> None:
del self.instrumentTabsOpen[name]
@QtCore.Slot(str, object, object, object)
- def onFuncCalled(self, n, args, kw, ret):
- if n == 'close_and_remove_instrument':
+ def onFuncCalled(self, n: str, args: Any, kw: Any, ret: Any) -> None:
+ if n == "close_and_remove_instrument":
for ins in args:
self.removeInstrumentFromGui(ins)
@QtCore.Slot(str)
- def closeInstrument(self, ins):
+ def closeInstrument(self, ins: str) -> None:
if ins in self.client.list_instruments():
self.client.close_instrument(ins)
@@ -762,7 +917,7 @@ def closeInstrument(self, ins):
class DetachedServerGui(QtWidgets.QMainWindow):
"""A detached version of the server gui."""
- def __init__(self, host: str = 'localhost', port: int = 5555):
+ def __init__(self, host: str = "localhost", port: int = 5555) -> None:
super().__init__()
self.instrumentTabsOpen: dict[str, GenericInstrument] = {}
@@ -770,7 +925,7 @@ def __init__(self, host: str = 'localhost', port: int = 5555):
self.client = Client(host, port, timeout=20)
self.subClient = None
- self.setWindowTitle('Instrument server detached')
+ self.setWindowTitle("Instrument server detached")
self.tabs = DetachableTabWidget(self)
self.tabs.onTabClosed.connect(self.onTabDeleted)
@@ -782,27 +937,28 @@ def __init__(self, host: str = 'localhost', port: int = 5555):
self.stationList.componentSelected.connect(self.displayComponentInfo)
self.stationList.itemDoubleClicked.connect(self.addInstrumentTab)
- stationWidgets = QtWidgets.QSplitter(QtCore.Qt.Horizontal)
+ stationWidgets = QtWidgets.QSplitter(QtCore.Qt.Orientation.Horizontal)
stationWidgets.addWidget(self.stationList)
stationWidgets.addWidget(self.stationObjInfo)
stationWidgets.setSizes([300, 500])
- self.tabs.addUnclosableTab(stationWidgets, 'Station')
+ self.tabs.addUnclosableTab(stationWidgets, "Station")
# Toolbar.
- self.toolBar = self.addToolBar('Tools')
- self.toolBar.setIconSize(QtCore.QSize(16, 16))
+ self.toolBar = self.addToolBar("Tools")
+ self.toolBar.setIconSize(QtCore.QSize(16, 16)) # type: ignore[union-attr]
# Station tools.
- self.toolBar.addWidget(QtWidgets.QLabel('Station:'))
+ self.toolBar.addWidget(QtWidgets.QLabel("Station:")) # type: ignore[union-attr]
self.refreshStationAction = QtWidgets.QAction(
- QtGui.QIcon(":/icons/refresh.svg"), 'Refresh', self)
+ QtGui.QIcon(":/icons/refresh.svg"), "Refresh", self
+ )
self.refreshStationAction.triggered.connect(self.refreshStationComponents)
- self.toolBar.addAction(self.refreshStationAction)
+ self.toolBar.addAction(self.refreshStationAction) # type: ignore[union-attr]
self.refreshStationComponents()
- def refreshStationComponents(self):
+ def refreshStationComponents(self) -> None:
"""Clear and re-populate the widget holding the station components, using
the objects that are currently registered in the station."""
self.stationList.clear()
@@ -812,12 +968,12 @@ def refreshStationComponents(self):
self.stationList.resizeColumnToContents(0)
@QtCore.Slot(str)
- def displayComponentInfo(self, name: Union[str, None]):
+ def displayComponentInfo(self, name: Union[str, None]) -> None:
if name is not None and name in self.client.list_instruments():
self.stationObjInfo.setObject(self.client.getBluePrint(name))
@QtCore.Slot(QtWidgets.QTreeWidgetItem, int)
- def addInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int):
+ def addInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int) -> None:
name = item.text(0)
if name not in self.instrumentTabsOpen:
ins = self.client.find_or_create_instrument(name)
@@ -825,16 +981,16 @@ def addInstrumentTab(self, item: QtWidgets.QTreeWidgetItem, index: int):
kwargs = {}
try:
guiConfig = self.client._getGuiConfig(name)
- moduleName = '.'.join(guiConfig['gui']['type'].split('.')[:-1])
- widgetClassName = guiConfig['gui']['type'].split('.')[-1]
+ moduleName = ".".join(guiConfig["gui"]["type"].split(".")[:-1])
+ widgetClassName = guiConfig["gui"]["type"].split(".")[-1]
module = importlib.import_module(moduleName)
widgetClass = getattr(module, widgetClassName)
- if 'kwargs' in guiConfig['gui']:
- kwargs = guiConfig[name]['gui']['kwargs']
+ if "kwargs" in guiConfig["gui"]:
+ kwargs = guiConfig[name]["gui"]["kwargs"]
# If the instrument does not have a guiconfig an exception is raised. just use defaults values
- except Exception as e:
+ except Exception:
pass
insWidget = widgetClass(ins, parent=self, **kwargs)
@@ -851,10 +1007,10 @@ def onTabDeleted(self, name: str) -> None:
del self.instrumentTabsOpen[name]
-def startServerGuiApplication(guiConfig: Optional[Dict[str, Dict[str, Any]]] = None,
- **serverKwargs: Any) -> "ServerGui":
- """Create a server gui window.
- """
+def startServerGuiApplication(
+ guiConfig: Optional[Dict[str, Dict[str, Any]]] = None, **serverKwargs: Any
+) -> "ServerGui":
+ """Create a server gui window."""
window = ServerGui(startServer=True, guiConfig=guiConfig, **serverKwargs)
window.show()
return window
@@ -865,19 +1021,21 @@ class EmbeddedClient(QtClient):
inside the server application."""
@QtCore.Slot(str)
- def start(self, addr: str):
- self.addr = "tcp://localhost:" + addr.split(':')[-1]
+ def start(self, addr: str) -> None:
+ if self._closed:
+ return
+ self.addr = "tcp://localhost:" + addr.split(":")[-1]
self.connect()
@QtCore.Slot(str)
- def ask(self, msg: str):
+ def ask(self, msg: str) -> Any:
logger.debug(f"Test client sending request: {msg}")
reply = super().ask(msg)
logger.debug(f"Test client received reply: {reply}")
return reply
-def bluePrintToHtml(bp: Union[ParameterBluePrint, InstrumentModuleBluePrint]):
+def bluePrintToHtml(bp: Union[ParameterBluePrint, InstrumentModuleBluePrint]) -> str:
header = f"""
@@ -895,13 +1053,13 @@ def bluePrintToHtml(bp: Union[ParameterBluePrint, InstrumentModuleBluePrint]):
return header + instrumentToHtml(bp) + footer
-def parameterToHtml(bp: ParameterBluePrint, headerLevel=None):
+def parameterToHtml(bp: ParameterBluePrint, headerLevel: Optional[int] = None) -> str:
setget = []
- setgetstr = ''
+ setgetstr = ""
if bp.gettable:
- setget.append('get')
+ setget.append("get")
if bp.settable:
- setget.append('set')
+ setget.append("set")
if len(setget) > 0:
setgetstr = f"[{', '.join(setget)}]"
@@ -924,7 +1082,7 @@ def parameterToHtml(bp: ParameterBluePrint, headerLevel=None):
return ret + var
-def instrumentToHtml(bp: InstrumentModuleBluePrint):
+def instrumentToHtml(bp: InstrumentModuleBluePrint) -> str:
ret = f"""
{bp.name}
@@ -996,4 +1154,4 @@ def instrumentToHtml(bp: InstrumentModuleBluePrint):
div.instrument_container {
padding: 10px;
}
-"""
\ No newline at end of file
+"""
diff --git a/instrumentserver/server/core.py b/src/instrumentserver/server/core.py
similarity index 71%
rename from instrumentserver/server/core.py
rename to src/instrumentserver/server/core.py
index 1d6b3c9..52342bb 100644
--- a/instrumentserver/server/core.py
+++ b/src/instrumentserver/server/core.py
@@ -15,40 +15,47 @@
# TODO: client white list
-import os
import importlib
import json
import logging
-import random
+import os
import queue
+import random
import socket
-
-from pathlib import Path
-from dataclasses import dataclass, field, fields
-from enum import Enum, unique
-from typing import Dict, Any, Union, Optional, Tuple, List, Callable
-from concurrent.futures import ThreadPoolExecutor
import threading
-
-import zmq
+from concurrent.futures import ThreadPoolExecutor
+from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import qcodes as qc
+import zmq
from qcodes import (
- Station, Instrument, InstrumentChannel, Parameter, ParameterWithSetpoints)
-from qcodes.instrument.base import InstrumentBase
-from qcodes.utils.validators import Validator
+ Parameter,
+ Station,
+)
from .. import QtCore, serialize
-from ..blueprints import (ParameterBluePrint, MethodBluePrint, InstrumentModuleBluePrint, ParameterBroadcastBluePrint,
- bluePrintFromMethod, bluePrintFromInstrumentModule, bluePrintFromParameter,
- INSTRUMENT_MODULE_BASE_CLASSES, PARAMETER_BASE_CLASSES, Operation,
- InstrumentCreationSpec, CallSpec, ParameterSerializeSpec, ServerInstruction, ServerResponse,)
-
-from ..base import send_router, recv_router, sendBroadcast
-from ..helpers import nestedAttributeFromString, objectClassPath, typeClassPath
-
-__author__ = 'Wolfgang Pfaff', 'Chao Zhou'
-__license__ = 'MIT'
+from ..base import recv_router, send_router, sendBroadcast
+from ..blueprints import (
+ INSTRUMENT_MODULE_BASE_CLASSES,
+ PARAMETER_BASE_CLASSES,
+ CallSpec,
+ InstrumentCreationSpec,
+ InstrumentModuleBluePrint,
+ MethodBluePrint,
+ Operation,
+ ParameterBluePrint,
+ ParameterBroadcastBluePrint,
+ ParameterSerializeSpec,
+ ServerInstruction,
+ ServerResponse,
+ bluePrintFromInstrumentModule,
+ bluePrintFromMethod,
+ bluePrintFromParameter,
+)
+from ..helpers import nestedAttributeFromString
+
+__author__ = "Wolfgang Pfaff", "Chao Zhou"
+__license__ = "MIT"
logger = logging.getLogger(__name__)
@@ -66,7 +73,7 @@ class StationServer(QtCore.QObject):
# If this string is sent as message to the server, it'll shut down and close
# the socket. Should only be used from within this module.
# It's randomized in the instantiated server for a little bit of safety.
- SAFEWORD = 'BANANA'
+ SAFEWORD = "BANANA"
#: Signal(str, str) -- emit messages for display in the gui (or other stuff the gui
#: wants to do with it.
@@ -96,31 +103,34 @@ class StationServer(QtCore.QObject):
#: Arguments: full function location as string, arguments, kw arguments, return value.
funcCalled = QtCore.Signal(str, object, object, object)
- def __init__(self,
- parent: Optional[QtCore.QObject] = None,
- port: int = 5555,
- allowUserShutdown: bool = False,
- addresses: List[str] = [],
- initScript: Optional[str] = None,
- serverConfig: Optional[Dict[str, Any]] = None,
- stationConfig: Optional[str] = None,
- guiConfig: Optional[dict[str, Any]] = None,
- pollingThread: Optional[QtCore.QThread] = None,
- ipAddresses: Optional[Dict[str, str]] = None
- ) -> None:
+ def __init__(
+ self,
+ parent: Optional[QtCore.QObject] = None,
+ port: int = 5555,
+ allowUserShutdown: bool = False,
+ addresses: List[str] = [],
+ initScript: Optional[str] = None,
+ serverConfig: Optional[Dict[str, Any]] = None,
+ stationConfig: Optional[str] = None,
+ guiConfig: Optional[dict[str, Any]] = None,
+ pollingThread: Optional[QtCore.QThread] = None,
+ ipAddresses: Optional[Dict[str, str]] = None,
+ ) -> None:
super().__init__(parent)
if addresses is None:
addresses = []
if initScript is None:
- initScript = ''
+ initScript = ""
- if (ipAddresses is not None
- and 'listeningAddress' in ipAddresses
- and (listening_addr := ipAddresses.get('listeningAddress')) is not None):
+ if (
+ ipAddresses is not None
+ and "listeningAddress" in ipAddresses
+ and (listening_addr := ipAddresses.get("listeningAddress")) is not None
+ ):
addresses.append(listening_addr)
- self.SAFEWORD = ''.join(random.choices([chr(i) for i in range(65, 91)], k=16))
+ self.SAFEWORD = "".join(random.choices([chr(i) for i in range(65, 91)], k=16))
self.serverRunning = False
self.port = int(port)
self.serverConfig = serverConfig
@@ -132,19 +142,23 @@ def __init__(self,
# For now the only server configs are whether to start an instrument.
if self.serverConfig is not None:
for instrumentName, settings in self.serverConfig.items():
- if settings['initialize']:
+ if settings["initialize"]:
self.station.load_instrument(instrumentName)
self.allowUserShutdown = allowUserShutdown
- self.listenAddresses = list(set(['127.0.0.1'] + addresses))
+ self.listenAddresses = list(set(["127.0.0.1"] + addresses))
self.initScript = initScript
self.broadcastPort = self.port + 1
self.broadcastSocket: zmq.Socket | None = None
self.externalBroadcastAddr = None
- if ipAddresses is not None and 'externalBroadcast' in ipAddresses and ipAddresses.get('externalBroadcast') is not None:
- self.externalBroadcastAddr = ipAddresses.get('externalBroadcast')
+ if (
+ ipAddresses is not None
+ and "externalBroadcast" in ipAddresses
+ and ipAddresses.get("externalBroadcast") is not None
+ ):
+ self.externalBroadcastAddr = ipAddresses.get("externalBroadcast")
self.externalBroadcastSocket: zmq.Socket | None = None
self.pollingThread = pollingThread
@@ -158,23 +172,23 @@ def __init__(self,
lambda n, v: logger.info(f"Parameter '{n}' retrieved: {str(v)}")
)
self.funcCalled.connect(
- lambda n, args, kw, ret: logger.info(f"Function called:"
- f"'{n}', args: {str(args)}, "
- f"kwargs: {str(kw)})'.")
+ lambda n, args, kw, ret: logger.info(
+ f"Function called:'{n}', args: {str(args)}, kwargs: {str(kw)})'."
+ )
)
-
+
# a queue for responses that are ready to be sent to client
- self._response_queue = queue.Queue()
+ self._response_queue: queue.Queue = queue.Queue()
# a socket pair for immediate wakeup of the main thread that sends response to client
self._wakeup_r, self._wakeup_w = socket.socketpair()
self._wakeup_r.setblocking(False)
self._wakeup_w.setblocking(False)
-
+
# Per-instrument locks to avoid races when multiple threads talk to the same instrument concurrently
self._instrument_locks: dict[str, threading.RLock] = {}
self._instrument_locks_lock = threading.Lock()
- def _runInitScript(self):
+ def _runInitScript(self) -> None:
if os.path.exists(self.initScript):
path = os.path.abspath(self.initScript)
env = dict(station=self.station)
@@ -187,7 +201,7 @@ def startServer(self) -> bool:
"""Start the server. This function does not return until the ZMQ server
has been shut down."""
- logger.info(f"Starting server.")
+ logger.info("Starting server.")
logger.info(f"The safe word is: {self.SAFEWORD}")
context = zmq.Context()
socket = context.socket(zmq.ROUTER)
@@ -213,25 +227,29 @@ def startServer(self) -> bool:
self.externalBroadcastSocket = context.socket(zmq.PUB)
self.externalBroadcastSocket.bind(self.externalBroadcastAddr)
else:
- logger.info(f"Not broadcasting to external address")
+ logger.info("Not broadcasting to external address")
self.serverRunning = True
- if self.initScript not in ['', None]:
- logger.info(f"Running init script")
+ if self.initScript not in ["", None]:
+ logger.info("Running init script")
self._runInitScript()
-
+
# create a thread pool for handling incoming client requests concurrently
with ThreadPoolExecutor() as pool:
while self.serverRunning or not self._response_queue.empty():
try:
# check if there is either incoming request from client, or a processing worker has finished
socks = dict(poller.poll(10))
-
+
# handle router socket events (incoming requests)
- if self.serverRunning and socket in socks and (socks[socket] & zmq.POLLIN):
+ if (
+ self.serverRunning
+ and socket in socks
+ and (socks[socket] & zmq.POLLIN)
+ ):
identity, message = recv_router(socket)
pool.submit(self._handleRouterMessage, identity, message)
-
+
# handle wakeup events (one or more workers finished)
if self._wakeup_r in socks and (socks[self._wakeup_r] & zmq.POLLIN):
# Drain the wakeup pipe so it doesn't stay "always readable"
@@ -240,58 +258,71 @@ def startServer(self) -> bool:
self._wakeup_r.recv(1024)
except BlockingIOError:
pass
-
+
# drain completed responses from workers
while True:
try:
- identity, response_to_client, response_log, shutdown = self._response_queue.get_nowait()
+ identity, response_to_client, response_log, shutdown = (
+ self._response_queue.get_nowait()
+ )
except queue.Empty:
break
-
+
try:
send_router(socket, identity, response_to_client)
except Exception as e:
logger.error(f"Failed to send response to client: {e}")
-
+
# emit log signal
- self.messageReceived.emit(str(response_to_client.message), response_log)
-
+ self.messageReceived.emit(
+ str(response_to_client.message), response_log
+ )
+
# flip the shutdown flag in the main thread
if shutdown:
self.serverRunning = False
-
+
except Exception as e:
logger.exception(f"Unexpected error in server loop: {e}")
break
-
- socket.close()
+
+ socket.close(linger=0)
self._wakeup_r.close()
self._wakeup_w.close()
- self.broadcastSocket.close()
+ self.broadcastSocket.close(linger=0)
+ if self.externalBroadcastSocket is not None:
+ try:
+ self.externalBroadcastSocket.close(linger=0)
+ except Exception:
+ pass
+ try:
+ context.destroy(linger=0)
+ except Exception:
+ pass
self.finished.emit()
logger.info("StationServer shut down cleanly.")
return True
-
- def _handleRouterMessage(self, identity, message):
+
+ def _handleRouterMessage(self, identity: bytes, message: Any) -> None:
"""
Handle a router message and put the response message in the response queue.
-
+
"""
message_ok = True
response_to_client = None
response_log = None
- shutdown = False # flag for letting the main thread shut down the server
+ shutdown = False # flag for letting the main thread shut down the server
# Allow the test client from within the same process to make sure the
# server shuts down.
if message == self.SAFEWORD:
- response_log = 'Server has received the safeword and will shut down.'
+ response_log = "Server has received the safeword and will shut down."
response_to_client = ServerResponse(message=response_log)
shutdown = True
logger.warning(response_log)
- elif self.allowUserShutdown and message == 'SHUTDOWN':
- response_log = 'Server shutdown requested by client.'
+ elif self.allowUserShutdown and message == "SHUTDOWN":
+ response_log = "Server shutdown requested by client."
response_to_client = ServerResponse(message=response_log)
shutdown = True
logger.warning(response_log)
@@ -308,13 +339,13 @@ def _handleRouterMessage(self, identity, message):
instruction = message
try:
instruction.validate()
- logger.debug(f"Received request for operation: "
- f"{str(instruction.operation)}")
- logger.debug(f"Instruction received: "
- f"{str(instruction)}")
+ logger.debug(
+ f"Received request for operation: {str(instruction.operation)}"
+ )
+ logger.debug(f"Instruction received: {str(instruction)}")
except Exception as e:
message_ok = False
- response_log = f'Received invalid message. Error raised: {str(e)}'
+ response_log = f"Received invalid message. Error raised: {str(e)}"
response_to_client = ServerResponse(message=None, error=e)
logger.warning(response_log)
@@ -324,17 +355,17 @@ def _handleRouterMessage(self, identity, message):
response_to_client = self.executeServerInstruction(instruction)
response_log = f"Response to client: {str(response_to_client)}"
if response_to_client.error is None:
- logger.debug(f"Response sent to client.")
+ logger.debug("Response sent to client.")
logger.debug(response_log)
else:
logger.warning(response_log)
else:
- response_log = f"Invalid message type."
+ response_log = "Invalid message type."
response_to_client = ServerResponse(message=None, error=response_log)
logger.warning(f"Invalid message type: {type(message)}.")
logger.debug(f"Invalid message received: {str(message)}")
-
+
self._response_queue.put((identity, response_to_client, response_log, shutdown))
# wake up the server loop so it can send the response immediately
try:
@@ -342,9 +373,10 @@ def _handleRouterMessage(self, identity, message):
except OSError:
# If we're shutting down / socket closed, ignore
pass
-
- def executeServerInstruction(self, instruction: ServerInstruction) \
- -> Tuple[ServerResponse, str]:
+
+ def executeServerInstruction(
+ self, instruction: ServerInstruction
+ ) -> ServerResponse:
"""
This is the interpreter function that the server will call to translate the
dictionary received from the proxy to instrument calls.
@@ -403,59 +435,71 @@ def _getExistingInstruments(self) -> List[str]:
def _createInstrument(self, spec: InstrumentCreationSpec) -> None:
"""Create a new instrument on the server."""
- sep_class = spec.instrument_class.split('.')
- modName = '.'.join(sep_class[:-1])
+ sep_class = spec.instrument_class.split(".")
+ modName = ".".join(sep_class[:-1])
clsName = sep_class[-1]
mod = importlib.import_module(modName)
cls = getattr(mod, clsName)
args = [] if spec.args is None else spec.args
kwargs = dict() if spec.kwargs is None else spec.kwargs
-
+
# lock based on the intended instrument name
lock = self._get_lock_for_target(spec.name)
if lock is None:
# in case name isn't in station yet, just guard creation with the dict lock
- lock = self._instrument_locks_lock # coarse but fine for this rare operation
+ lock = (
+ self._instrument_locks_lock # type: ignore[assignment]
+ ) # coarse but fine for this rare operation
- with lock:
+ with lock: # type: ignore[union-attr]
new_instrument = qc.find_or_create_instrument(
- cls, spec.name, *args, **kwargs)
-
+ cls, spec.name, *args, **kwargs
+ )
+
if new_instrument.name not in self.station.components:
self.station.add_component(new_instrument)
-
- self.instrumentCreated.emit(bluePrintFromInstrumentModule(new_instrument.name, new_instrument),
- args, kwargs)
+
+ self.instrumentCreated.emit(
+ bluePrintFromInstrumentModule(new_instrument.name, new_instrument),
+ args,
+ kwargs,
+ )
def _callObject(self, spec: CallSpec) -> Any:
"""Call some callable found in the station."""
obj = nestedAttributeFromString(self.station, spec.target)
args = spec.args if spec.args is not None else []
kwargs = spec.kwargs if spec.kwargs is not None else {}
-
- def _invoke():
+
+ def _invoke() -> Any:
ret = obj(*args, **kwargs)
-
+
# Check if a new parameter is being created.
self._newOrDeleteParameterDetection(spec, args, kwargs)
-
+
if isinstance(obj, Parameter):
if len(args) > 0:
self.parameterSet.emit(spec.target, args[0])
-
+
# Broadcast changes in parameter values.
- self._broadcastParameterChange(ParameterBroadcastBluePrint(spec.target, 'parameter-update', args[0]))
+ self._broadcastParameterChange(
+ ParameterBroadcastBluePrint(
+ spec.target, "parameter-update", args[0]
+ )
+ )
else:
self.parameterGet.emit(spec.target, ret)
-
+
# Broadcast calls of parameters.
- self._broadcastParameterChange(ParameterBroadcastBluePrint(spec.target, 'parameter-call', ret))
+ self._broadcastParameterChange(
+ ParameterBroadcastBluePrint(spec.target, "parameter-call", ret)
+ )
else:
self.funcCalled.emit(spec.target, args, kwargs, ret)
-
+
return ret
-
+
# Get the appropriate per-instrument lock, if any
lock = self._get_lock_for_target(spec.target)
if lock is None:
@@ -466,28 +510,30 @@ def _invoke():
with lock:
return _invoke()
- def _getBluePrint(self, path: str) -> Union[InstrumentModuleBluePrint,
- ParameterBluePrint,
- MethodBluePrint]:
+ def _getBluePrint(
+ self, path: str
+ ) -> Union[InstrumentModuleBluePrint, ParameterBluePrint, MethodBluePrint]:
logger.debug(f"Fetching blueprint for: {path}")
obj = nestedAttributeFromString(self.station, path)
if isinstance(obj, tuple(INSTRUMENT_MODULE_BASE_CLASSES)):
- instrument_blueprint = bluePrintFromInstrumentModule(path, obj)
+ instrument_blueprint = bluePrintFromInstrumentModule(path, obj) # type: ignore[arg-type]
if instrument_blueprint is None:
- raise ValueError(f'Failed to create blueprint for instrument module {path}')
+ raise ValueError(
+ f"Failed to create blueprint for instrument module {path}"
+ )
return instrument_blueprint
elif isinstance(obj, tuple(PARAMETER_BASE_CLASSES)):
- parameter_blueprint = bluePrintFromParameter(path, obj)
+ parameter_blueprint = bluePrintFromParameter(path, obj) # type: ignore[arg-type]
if parameter_blueprint is None:
- raise ValueError(f'Failed to create blueprint for parameter {path}')
+ raise ValueError(f"Failed to create blueprint for parameter {path}")
return parameter_blueprint
elif callable(obj):
method_blueprint = bluePrintFromMethod(path, obj)
if method_blueprint is None:
- raise ValueError(f'Failed to create blueprint for method {path}')
+ raise ValueError(f"Failed to create blueprint for method {path}")
return method_blueprint
else:
- raise ValueError(f'Cannot create a blueprint for {type(obj)}')
+ raise ValueError(f"Cannot create a blueprint for {type(obj)}")
def _toParamDict(self, opts: ParameterSerializeSpec) -> Dict[str, Any]:
obj: list[Any] | Station
@@ -496,13 +542,13 @@ def _toParamDict(self, opts: ParameterSerializeSpec) -> Dict[str, Any]:
else:
obj = [nestedAttributeFromString(self.station, opts.path)]
- includeMeta = [k for k in opts.attrs if k != 'value']
+ includeMeta = [k for k in opts.attrs if k != "value"]
args = opts.args if opts.args else []
kwargs = dict(opts.kwargs) if opts.kwargs else {}
kwargs.update(includeMeta=includeMeta)
return serialize.toParamDict(obj, *args, **kwargs)
- def _fromParamDict(self, params: Dict[str, Any]):
+ def _fromParamDict(self, params: Dict[str, Any]) -> None:
return serialize.fromParamDict(params, self.station)
def _getGuiConfig(self, instrumentName: str) -> str:
@@ -511,7 +557,7 @@ def _getGuiConfig(self, instrumentName: str) -> str:
"""
if self.station is None:
raise ValueError("Station is not initialized.")
-
+
if instrumentName not in self.station.components:
raise ValueError(f"Instrument {instrumentName} not found in station.")
@@ -521,7 +567,7 @@ def _getGuiConfig(self, instrumentName: str) -> str:
return json.dumps(self.guiConfig[instrumentName])
- def _broadcastParameterChange(self, blueprint: ParameterBroadcastBluePrint):
+ def _broadcastParameterChange(self, blueprint: ParameterBroadcastBluePrint) -> None:
"""
Broadcast any changes to parameters in the server.
The message is composed of a 2 part array. The first item is the name of the instrument the parameter is from,
@@ -530,13 +576,23 @@ def _broadcastParameterChange(self, blueprint: ParameterBroadcastBluePrint):
:param blueprint: The parameter broadcast blueprint that is being broadcast
"""
- sendBroadcast(self.broadcastSocket, blueprint.name.split('.')[0], blueprint)
+ assert self.broadcastSocket is not None
+ sendBroadcast(self.broadcastSocket, blueprint.name.split(".")[0], blueprint)
if self.externalBroadcastAddr is not None:
- sendBroadcast(self.externalBroadcastSocket, blueprint.name.split('.')[0], blueprint)
- logger.info(f"Parameter {blueprint.name} has broadcast an update of type: {blueprint.action},"
- f" with a value: {blueprint.value}.")
+ assert self.externalBroadcastSocket is not None
+ sendBroadcast(
+ self.externalBroadcastSocket,
+ blueprint.name.split(".")[0],
+ blueprint,
+ )
+ logger.info(
+ f"Parameter {blueprint.name} has broadcast an update of type: {blueprint.action},"
+ f" with a value: {blueprint.value}."
+ )
- def _newOrDeleteParameterDetection(self, spec, args, kwargs):
+ def _newOrDeleteParameterDetection(
+ self, spec: CallSpec, args: List[Any], kwargs: Dict[str, Any]
+ ) -> None:
"""
Detects if the call action is being used to create a new parameter or deletes an existing parameter.
If so, it creates the parameter broadcast blueprint and broadcast it.
@@ -546,19 +602,17 @@ def _newOrDeleteParameterDetection(self, spec, args, kwargs):
:param kwargs: kwargs being passed to the call method.
"""
- if spec.target.split('.')[-1] == 'add_parameter':
- name = spec.target.split('.')[0] + '.' + '.'.join(spec.args)
- pb = ParameterBroadcastBluePrint(name,
- 'parameter-creation',
- kwargs['initial_value'],
- kwargs['unit'])
+ if spec.target.split(".")[-1] == "add_parameter":
+ name = spec.target.split(".")[0] + "." + ".".join(spec.args) # type: ignore[arg-type]
+ pb = ParameterBroadcastBluePrint(
+ name, "parameter-creation", kwargs["initial_value"], kwargs["unit"]
+ )
self._broadcastParameterChange(pb)
- elif spec.target.split('.')[-1] == 'remove_parameter':
- name = spec.target.split('.')[0] + '.' + '.'.join(spec.args)
- pb = ParameterBroadcastBluePrint(name,
- 'parameter-deletion')
+ elif spec.target.split(".")[-1] == "remove_parameter":
+ name = spec.target.split(".")[0] + "." + ".".join(spec.args) # type: ignore[arg-type]
+ pb = ParameterBroadcastBluePrint(name, "parameter-deletion")
self._broadcastParameterChange(pb)
-
+
def _get_lock_for_target(self, target: str) -> Optional[threading.RLock]:
"""
Given a call target like 'dac1.ch1.offset' or 'awg.ch2.set_sq_wave',
@@ -572,8 +626,8 @@ def _get_lock_for_target(self, target: str) -> Optional[threading.RLock]:
return None
# First token before the first dot: assumed to be instrument name
- root = target.split('.')[0]
-
+ root = target.split(".")[0]
+
# Only lock if this actually corresponds to an instrument in the station
if root not in self.station.components:
return None
@@ -585,32 +639,36 @@ def _get_lock_for_target(self, target: str) -> Optional[threading.RLock]:
self._instrument_locks[root] = lock
return lock
-def startServer(port: int = 5555,
- allowUserShutdown: bool = False,
- addresses: List[str] = [],
- initScript: Optional[str] = None,
- serverConfig: Optional[Dict[str, Any]] = None,
- stationConfig: Optional[str] = None,
- guiConfig: Optional[dict[str, Any]] = None,
- pollingThread: Optional[QtCore.QThread] = None,
- ipAddresses: Optional[Dict[str, str]] = None) -> \
- Tuple[StationServer, QtCore.QThread]:
+
+def startServer(
+ port: int = 5555,
+ allowUserShutdown: bool = False,
+ addresses: List[str] = [],
+ initScript: Optional[str] = None,
+ serverConfig: Optional[Dict[str, Any]] = None,
+ stationConfig: Optional[str] = None,
+ guiConfig: Optional[dict[str, Any]] = None,
+ pollingThread: Optional[QtCore.QThread] = None,
+ ipAddresses: Optional[Dict[str, str]] = None,
+) -> Tuple[StationServer, QtCore.QThread]:
"""Create a server and run in a separate thread.
:returns: The server object and the thread it's running in.
"""
- server = StationServer(port=port,
- allowUserShutdown=allowUserShutdown,
- addresses=addresses,
- initScript=initScript,
- serverConfig=serverConfig,
- stationConfig=stationConfig,
- guiConfig=guiConfig,
- pollingThread=pollingThread,
- ipAddresses=ipAddresses)
+ server = StationServer(
+ port=port,
+ allowUserShutdown=allowUserShutdown,
+ addresses=addresses,
+ initScript=initScript,
+ serverConfig=serverConfig,
+ stationConfig=stationConfig,
+ guiConfig=guiConfig,
+ pollingThread=pollingThread,
+ ipAddresses=ipAddresses,
+ )
thread = QtCore.QThread()
server.moveToThread(thread)
server.finished.connect(thread.quit)
- thread.started.connect(server.startServer)
+ thread.started.connect(server.startServer) # type: ignore[arg-type]
thread.start()
return server, thread
diff --git a/instrumentserver/server/pollingWorker.py b/src/instrumentserver/server/pollingWorker.py
similarity index 72%
rename from instrumentserver/server/pollingWorker.py
rename to src/instrumentserver/server/pollingWorker.py
index b790db7..2e97fad 100644
--- a/instrumentserver/server/pollingWorker.py
+++ b/src/instrumentserver/server/pollingWorker.py
@@ -2,7 +2,6 @@
from typing import Dict, Optional
from .. import QtCore
-
from ..client import Client
from ..helpers import nestedAttributeFromString
@@ -10,14 +9,16 @@
class PollingWorker(QtCore.QThread):
- def __init__(self, pollingRates: Optional[Dict[str, int]]=None):
+ def __init__(self, pollingRates: Optional[Dict[str, int]] = None):
super().__init__(None)
# This worker is supposed to only run through the server itself so there is no need to change the defaults of the client.
- self.cli = Client(raise_exceptions=False,timeout=60000) # Don't raise exceptions on timeouts
+ self.cli = Client(
+ raise_exceptions=False, timeout=60000
+ ) # Don't raise exceptions on timeouts
self.pollingRates = pollingRates
# Used by the qtimers, get value of the param
- def getParamValue(self, paramName):
+ def getParamValue(self, paramName: str) -> None:
try:
parts = paramName.split(".")
instr = self.cli.find_or_create_instrument(parts[0])
@@ -31,26 +32,28 @@ def getParamValue(self, paramName):
# Don't re-raise the exception, just log it and continue
# Creates a qtimer for each param in the dict with the interval specified
- def run(self):
+ def run(self) -> None:
timers = []
# Deletes param from dict if it does not exist
delList = []
- for param in self.pollingRates:
+ for param in self.pollingRates: # type: ignore[union-attr]
if param not in self.cli.getParamDict(param.split(".")[0]):
logger.warning(f"Parameter {param} does not exist")
delList.append(param)
for item in delList:
- del self.pollingRates[item]
-
+ del self.pollingRates[item] # type: ignore[union-attr]
+
# Prints which parameters are being polled
- logger.info(f"Broadcasting the following parameters: {list(self.pollingRates.keys())}")
+ logger.info(
+ f"Broadcasting the following parameters: {list(self.pollingRates.keys())}" # type: ignore[union-attr]
+ )
# Creates timers for each param in the dict
- for param in self.pollingRates:
+ for param in self.pollingRates: # type: ignore[union-attr]
timer = QtCore.QTimer()
timer.timeout.connect(lambda name=param: self.getParamValue(name))
- timer.start(int(self.pollingRates.get(param) * 1000))
+ timer.start(int(self.pollingRates.get(param) * 1000)) # type: ignore[union-attr,operator]
timers.append(timer)
self.exec_()
diff --git a/instrumentserver/testing/dummy_instruments/__init__.py b/src/instrumentserver/testing/__init__.py
similarity index 100%
rename from instrumentserver/testing/dummy_instruments/__init__.py
rename to src/instrumentserver/testing/__init__.py
diff --git a/instrumentserver/testing/create_instrument.py b/src/instrumentserver/testing/create_instrument.py
similarity index 64%
rename from instrumentserver/testing/create_instrument.py
rename to src/instrumentserver/testing/create_instrument.py
index 9949f7b..bb7b366 100644
--- a/instrumentserver/testing/create_instrument.py
+++ b/src/instrumentserver/testing/create_instrument.py
@@ -1,4 +1,5 @@
from instrumentserver.client import Client as InstrumentClient
+
"""
Script used to create an instrument in the instrument server used for developing the dashboard/logger.
"""
@@ -7,10 +8,10 @@
# used for testing, the instruments should be already created for the dashboard to work
cli = InstrumentClient()
-if 'test' in cli.list_instruments():
- instrument = cli.get_instrument('test')
+if "test" in cli.list_instruments():
+ instrument = cli.get_instrument("test")
else:
instrument = cli.find_or_create_instrument(
- 'test'
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentRandomNumber',)
-
+ "test"
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentRandomNumber",
+ )
diff --git a/src/instrumentserver/testing/dummy_instruments/__init__.py b/src/instrumentserver/testing/dummy_instruments/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/instrumentserver/testing/dummy_instruments/generic.py b/src/instrumentserver/testing/dummy_instruments/generic.py
new file mode 100644
index 0000000..e20c46c
--- /dev/null
+++ b/src/instrumentserver/testing/dummy_instruments/generic.py
@@ -0,0 +1,253 @@
+# mypy: ignore-errors
+# No need to mypy check dummy testing instruments.
+
+import time
+from typing import List
+
+import numpy as np
+from qcodes import Instrument
+from qcodes.math_utils.field_vector import FieldVector
+from qcodes.utils import validators
+
+
+class DummyChannel(Instrument):
+ def __init__(self, name: str, *args, **kwargs):
+ super().__init__(name, *args, **kwargs)
+ self.add_parameter(
+ "ch0", set_cmd=None, vals=validators.Numbers(0, 1), initial_value=0
+ )
+
+ self.add_parameter(
+ "ch1",
+ unit="v",
+ set_cmd=None,
+ vals=validators.Numbers(-1, 1),
+ initial_value=1,
+ )
+
+ def dummy_function(self, *args, **kwargs):
+ """Dummy function for specific channels used for testing"""
+ print(f"the dummy chanel: {self.name} has been activated with:")
+ print(f"args: {args}")
+ print(f"kwargs: {kwargs}")
+ return True
+
+
+class DummyInstrumentWithSubmodule(Instrument):
+ """A dummy instrument with submodules."""
+
+ def __init__(
+ self, name: str, address=None, first_arg=None, second_arg=None, *args, **kwargs
+ ):
+ super().__init__(name, *args, **kwargs)
+ self.address = address
+
+ self.first_arg = first_arg
+ self.second_arg = second_arg
+
+ self.add_parameter(
+ "param0", set_cmd=None, vals=validators.Numbers(0, 1), initial_value=0
+ )
+
+ self.add_parameter(
+ "param1",
+ unit="v",
+ set_cmd=None,
+ vals=validators.Numbers(-1, 1),
+ initial_value=1,
+ )
+
+ self.add_parameter(
+ "int_param1",
+ unit="v",
+ set_cmd=None,
+ vals=validators.Ints(-200, 200),
+ initial_value=1,
+ )
+
+ for chan_name in ("A", "B", "C"):
+ channel = DummyChannel(f"{name}_Chan{chan_name}")
+ self.add_submodule(chan_name, channel)
+
+ def close(self) -> None:
+ for submodule in list(getattr(self, "submodules", {}).values()):
+ try:
+ submodule.close()
+ except Exception:
+ pass
+ super().close()
+
+ def ask_raw(self, cmd):
+ """Dummy ask_raw so *IDN? and similar SCPI queries don't explode the GUI."""
+ if cmd.strip().upper().startswith("*IDN"):
+ return f"dummy,{self.name},0,0"
+ return ""
+
+ def test_func(self, a, b, *args, c: List[int] = [10, 11], **kwargs):
+ """
+ This is a test function, of course you knew this from the tittle but It's nice to have documentation, isn't it?
+
+ :param a: Very nice parameter.
+ :param b: Even nicer parameter
+ :param c: This one sucks though.
+ """
+ return a, b, args[0], c, kwargs["d"], self.param0()
+
+ def dummy_function(self, *args, **kwargs):
+ """
+ Such a dumb dummy function here doing nothing other than printing and occupying your precious, precious terminal
+ space.
+ """
+ print(f"the dummy chanel: {self.name} has been activated with:")
+ print(f"args: {args}")
+ print(f"kwargs: {kwargs}")
+ return self.address, self.first_arg, self.second_arg
+
+
+class DummyInstrumentTimeout(Instrument):
+ """A dummy instrument to test timeout situations."""
+
+ def __init__(self, name: str, *args, **kwargs):
+ super().__init__(name, *args, **kwargs)
+
+ self.random = np.random.randint(10000)
+ self._param1 = 1
+ self._param2 = 2
+ self._p1_get_counter = 0
+
+ self.add_parameter("random_int", get_cmd=self.get_random)
+ self.add_parameter(
+ "param1",
+ get_cmd=self._get_param1,
+ set_cmd=lambda p: setattr(self, "_param1", p),
+ )
+ self.add_parameter(
+ "param2",
+ get_cmd=lambda: self._param2,
+ set_cmd=lambda p: setattr(self, "_param2", p),
+ )
+
+ def _get_param1(self):
+ # for testing potentially redundant/duplicate get calls
+ print(
+ f"-------------- getting {self.name}.param1, count {self._p1_get_counter}----------------"
+ )
+ self._p1_get_counter += 1
+ return self._param1
+
+ def get_random(self):
+ return self.random
+
+ def get_random_timeout(self, wait_time=10):
+ time.sleep(wait_time)
+ return self.get_random()
+
+
+class DummyInstrumentRandomNumber(Instrument):
+ """A dummy instrument with a few parameters that have random numbers generated on demand"""
+
+ def __init__(self, name: str, *args, **kwargs):
+ super().__init__(name, *args, **kwargs)
+
+ self.add_parameter(
+ "param0", set_cmd=None, vals=validators.Numbers(1, 10), initial_value=1
+ )
+
+ self.add_parameter(
+ "param1", set_cmd=None, vals=validators.Numbers(10, 20), initial_value=10
+ )
+
+ self.add_parameter(
+ "param2", set_cmd=None, vals=validators.Numbers(20, 30), initial_value=20
+ )
+
+ self.add_parameter(
+ "param3", set_cmd=None, vals=validators.Numbers(30, 40), initial_value=30
+ )
+
+ self.add_parameter(
+ "param4", set_cmd=None, vals=validators.Numbers(40, 50), initial_value=40
+ )
+
+ def generate_data(self, name: str):
+
+ if name == "param0":
+ self.parameters[name].set(np.random.randint(1, 10))
+ if name == "param1":
+ self.parameters[name].set(np.random.randint(10, 20))
+ if name == "param2":
+ self.parameters[name].set(np.random.randint(20, 30))
+ if name == "param3":
+ self.parameters[name].set(np.random.randint(30, 40))
+ if name == "param4":
+ self.parameters[name].set(np.random.randint(40, 50))
+
+ def get(self, param_name):
+ self.generate_data(param_name)
+ return self.parameters[param_name].get()
+
+
+class FieldVectorIns(Instrument):
+ """
+ class used to develop json serialization and guis
+ """
+
+ def __init__(self, name, starting_parameter=22, *args, **kwargs):
+ super().__init__(name=name, *args, **kwargs)
+
+ self.field_vector = FieldVector(x=1, y=1, z=1)
+ self.complex_value = 1 + 1j
+ self.complex_lst = [1 + 1j, -2 - 2j]
+ self.starting_parameter = starting_parameter
+
+ self.add_parameter(
+ name="field",
+ label="target field",
+ unit="T",
+ get_cmd=self.get_field,
+ set_cmd=self.set_field,
+ )
+
+ self.add_parameter(
+ name="complex",
+ label="complex value",
+ unit="",
+ get_cmd=self.get_complex,
+ set_cmd=self.set_complex,
+ )
+
+ self.add_parameter(
+ name="complex_list",
+ label="complex list",
+ unit="",
+ get_cmd=self.get_complex_list,
+ set_cmd=self.set_complex_list,
+ )
+
+ def get_starting_parameter(self):
+ return self.starting_parameter
+
+ def set_starting_parameter(self, new_value):
+ pass
+
+ def get_field(self):
+ return self.field_vector
+
+ def set_field(self, field_vector: FieldVector):
+ self.field_vector = field_vector
+
+ def get_complex(self):
+ return self.complex_value
+
+ def set_complex(self, value: complex):
+ self.complex_value = value
+
+ def get_complex_list(self):
+ return self.complex_lst
+
+ def set_complex_list(self, value):
+ self.complex_lst = value
+
+ def generic_function(self):
+ print("this generic function has been called")
+ return 3
diff --git a/src/instrumentserver/testing/dummy_instruments/rf.py b/src/instrumentserver/testing/dummy_instruments/rf.py
new file mode 100644
index 0000000..06564d1
--- /dev/null
+++ b/src/instrumentserver/testing/dummy_instruments/rf.py
@@ -0,0 +1,231 @@
+from typing import Any
+
+import numpy as np
+from numpy.typing import NDArray
+from qcodes import Instrument, ParameterWithSetpoints, find_or_create_instrument
+from qcodes.utils import validators
+from scipy import constants
+
+
+class ResonatorResponse(Instrument):
+ """A dummy instrument that generates the response of a resonator measured in
+ reflection.
+
+ Behavior is essentially that of a VNA, with the resonator and system
+ properties added as parameters.
+ """
+
+ def __init__(self, name: str, f0: float = 5e9, df: float = 1e6, **kw: Any) -> None:
+ super().__init__(name, **kw)
+
+ self._frq_mod = 0.0
+ self._frq_mod_multiply = False
+
+ # add params of the resonator and the virtual detection chain
+ self.add_parameter(
+ "resonator_frequency",
+ set_cmd=None,
+ unit="Hz",
+ vals=validators.Numbers(1, 50e9),
+ initial_value=f0,
+ )
+ self.add_parameter(
+ "resonator_linewidth",
+ set_cmd=None,
+ unit="Hz",
+ vals=validators.Numbers(1, 1e9),
+ initial_value=df,
+ )
+ self.add_parameter(
+ "noise_temperature",
+ set_cmd=None,
+ unit="K",
+ vals=validators.Numbers(0.05, 3000),
+ initial_value=4.0,
+ )
+ self.add_parameter(
+ "input_attenuation",
+ set_cmd=None,
+ unit="dB",
+ vals=validators.Numbers(0, 200),
+ initial_value=70,
+ )
+
+ # actual instrument parameters
+ self.add_parameter(
+ "start_frequency",
+ set_cmd=None,
+ unit="Hz",
+ vals=validators.Numbers(20e3, 19.999e9),
+ initial_value=20e3,
+ )
+ self.add_parameter(
+ "stop_frequency",
+ set_cmd=None,
+ unit="Hz",
+ vals=validators.Numbers(20.1e3, 20e9),
+ initial_value=20e9,
+ )
+ self.add_parameter(
+ "npoints", set_cmd=None, vals=validators.Ints(2, 40001), initial_value=1601
+ )
+ self.add_parameter(
+ "bandwidth",
+ set_cmd=None,
+ unit="Hz",
+ vals=validators.Numbers(1, 1e6),
+ initial_value=10e3,
+ )
+ self.add_parameter(
+ "power",
+ set_cmd=None,
+ unit="dBm",
+ vals=validators.Numbers(-100, 0),
+ initial_value=-100,
+ )
+
+ # data parameters
+ self.add_parameter(
+ "frequency",
+ unit="Hz",
+ vals=validators.Arrays(shape=(self.npoints.get_latest,)),
+ get_cmd=self._frequency_vals,
+ snapshot_value=False,
+ )
+ self.add_parameter(
+ "data",
+ parameter_class=ParameterWithSetpoints,
+ setpoints=[
+ self.frequency,
+ ],
+ vals=validators.Arrays(
+ shape=(self.npoints.get_latest,),
+ valid_types=[np.complexfloating],
+ ),
+ get_cmd=self._get_data,
+ )
+
+ def modulate_frequency(self, delta: float = 0, multiply: bool = False) -> None:
+ """Add an offset to the resonance frequency.
+
+ If `multiply` is ``True``, the change in frequency is the product of `delta`
+ and the set frequency. If ``False``, then `delta` is added.
+ """
+ self._frq_mod = delta
+ self._frq_mod_multiply = multiply
+
+ # private utility methods
+ def _frequency_vals(self) -> "NDArray[np.floating]":
+ return np.linspace(
+ self.start_frequency(), self.stop_frequency(), self.npoints()
+ )
+
+ def _get_data(self) -> "NDArray[np.complexfloating]":
+ f0 = self.resonator_frequency()
+ if self._frq_mod_multiply:
+ f0 *= self._frq_mod
+ else:
+ f0 += self._frq_mod
+
+ fvals = self._frequency_vals()
+ data = self._resonator_reflection_signal(
+ fvals,
+ f0,
+ self.resonator_linewidth(),
+ self.power() - self.input_attenuation(),
+ self.bandwidth(),
+ self.noise_temperature(),
+ )
+
+ return data
+
+ def _resonator_reflection_signal(
+ self,
+ fvals: "NDArray[np.floating]",
+ f0: float,
+ df: float,
+ P_in: float,
+ BW: float,
+ T_N: float,
+ ) -> "NDArray[np.complexfloating]":
+ """Compute a realistic resonator reflection signal of a one-port
+ resonator, including random noise.
+
+ :param fvals: probe frequencies [Hz]
+ :param f0: resonance frequency [Hz]
+ :param df: line width [Hz]
+ :param P_in: incident power [dBm]
+ :param BW: detection bandwidth [Hz]
+ :param T_N: noise temperature [K]
+
+ :returns: dummy data, same shape as `fvals`.
+ """
+ det = fvals - f0
+ pwr = 1e-3 * 10 ** (P_in / 10) # convert dBm to Watt
+ ideal_signal = (2j * det - df) / (2j * det + df)
+ noise = (constants.k * T_N * BW / pwr) ** 0.5
+ noise_real = np.random.normal(size=ideal_signal.size, loc=0, scale=noise)
+ noise_imag = np.random.normal(size=ideal_signal.size, loc=0, scale=noise)
+ return ideal_signal + noise_real + 1j * noise_imag
+
+
+class Generator(Instrument):
+ """A simple dummy that mocks an RF generator."""
+
+ def __init__(self, name: str, *arg: Any, **kw: Any) -> None:
+ super().__init__(name, *arg, **kw)
+
+ self.add_parameter(
+ "frequency",
+ unit="Hz",
+ set_cmd=None,
+ vals=validators.Numbers(1e3, 20e9),
+ initial_value=10e9,
+ )
+
+ self.add_parameter(
+ "power",
+ unit="dBm",
+ set_cmd=None,
+ vals=validators.Numbers(-100, 25),
+ initial_value=-100,
+ )
+
+ self.add_parameter(
+ "rf_on", set_cmd=None, vals=validators.Bool(), initial_value=False
+ )
+
+
+class FluxControl(Instrument):
+ """A dummy that hooks to :class:`.ResonatorResponse` and modifies its
+ resonance frequency as if the resonator were a squid."""
+
+ def __init__(
+ self, name: str, resonator_instrument: str, *args: Any, **kwargs: Any
+ ) -> None:
+ super().__init__(name, *args, **kwargs)
+
+ self._resonator = find_or_create_instrument(
+ instrument_class=ResonatorResponse, name=resonator_instrument
+ )
+
+ self.add_parameter(
+ "inductive_participation_ratio",
+ set_cmd=None,
+ vals=validators.Numbers(0, 1),
+ initial_value=0.05,
+ )
+
+ self.add_parameter(
+ "flux",
+ unit="Phi_0",
+ set_cmd=self._set_flux,
+ vals=validators.Numbers(-1, 1),
+ initial_value=0,
+ )
+
+ def _set_flux(self, flux: float) -> None:
+ mod = 1.0 / (
+ 1.0 + self.inductive_participation_ratio() / np.abs(np.cos(np.pi * flux))
+ )
+ self._resonator.modulate_frequency(mod, True)
diff --git a/test/client_workingdir/init_client.py b/test/client_workingdir/init_client.py
index fe06c22..f912ca8 100755
--- a/test/client_workingdir/init_client.py
+++ b/test/client_workingdir/init_client.py
@@ -1,39 +1,36 @@
# -*- coding: utf-8 -*-
-#%% Imports
+# %% Imports
import os
from qcodes import Instrument
-from instrumentserver.client import Client
-from instrumentserver.client import ProxyInstrument
+from instrumentserver.client import Client
-#%% Create all my instruments
+# %% Create all my instruments
Instrument.close_all()
ins_cli = Client()
dummy_vna = ins_cli.find_or_create_instrument(
- 'dummy_vna',
- 'instrumentserver.testing.dummy_instruments.rf.ResonatorResponse',
-
+ "dummy_vna",
+ "instrumentserver.testing.dummy_instruments.rf.ResonatorResponse",
)
dummy_multichan = ins_cli.find_or_create_instrument(
- 'dummy_multichan',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule',
-
+ "dummy_multichan",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
)
pm = ins_cli.find_or_create_instrument(
- 'pm',
- 'instrumentserver.params.ParameterManager',
+ "pm",
+ "instrumentserver.params.ParameterManager",
)
-#%% save the state
+# %% save the state
# Note: This now saves ALL instruments' parameters, not just pm
-ins_cli.paramsToFile(os.path.abspath('./parameters.json'))
+ins_cli.paramsToFile(os.path.abspath("./parameters.json"))
-#%% load pm settings from file
+# %% load pm settings from file
# Note: This now loads ALL instruments' parameters from file
-ins_cli.paramsFromFile(os.path.abspath('./parameters.json'))
+ins_cli.paramsFromFile(os.path.abspath("./parameters.json"))
diff --git a/test/notebooks/Autoupdate.ipynb b/test/notebooks/Autoupdate.ipynb
index a6e9328..a82f194 100644
--- a/test/notebooks/Autoupdate.ipynb
+++ b/test/notebooks/Autoupdate.ipynb
@@ -6,18 +6,7 @@
"id": "a537f51e-cd6f-406f-9bb7-59f80133e622",
"metadata": {},
"outputs": [],
- "source": [
- "from pprint import pprint\n",
- "import time\n",
- "\n",
- "import numpy as np\n",
- "from matplotlib import pyplot as plt\n",
- "import h5py\n",
- "\n",
- "\n",
- "from qcodes import Instrument, Station, find_or_create_instrument\n",
- "from plottr.data import datadict_storage as dds, datadict as dd"
- ]
+ "source": []
},
{
"cell_type": "code",
@@ -27,7 +16,8 @@
"outputs": [],
"source": [
"from instrumentserver.client import Client as InstrumentClient\n",
- "cli = InstrumentClient() # connect to default host (localhost) and default port (5555)"
+ "\n",
+ "cli = InstrumentClient() # connect to default host (localhost) and default port (5555)"
]
},
{
@@ -85,7 +75,9 @@
}
],
"source": [
- "params = cli.get_instrument('params') # 'params' is the name the startup script gave the instrument\n",
+ "params = cli.get_instrument(\n",
+ " \"params\"\n",
+ ") # 'params' is the name the startup script gave the instrument\n",
"\n",
"# simply output the value of the pi pulse length:\n",
"params.qubit.pipulse.len()"
@@ -200,7 +192,7 @@
"class MyData:\n",
" name: str\n",
" value: float\n",
- " \n",
+ "\n",
" def __repr__(self):\n",
" return str(self)\n",
"\n",
@@ -209,7 +201,7 @@
" 'name': {self.name},\n",
" 'value': {self.value},\n",
"}}\n",
- "\"\"\"\n"
+ "\"\"\""
]
},
{
@@ -219,7 +211,7 @@
"metadata": {},
"outputs": [],
"source": [
- "data = MyData(name='my name', value=1.23)"
+ "data = MyData(name=\"my name\", value=1.23)"
]
},
{
@@ -293,9 +285,7 @@
"id": "c8ee480c-13a5-4941-b3d2-d6dd85c359ac",
"metadata": {},
"outputs": [],
- "source": [
- "import zmq"
- ]
+ "source": []
},
{
"cell_type": "code",
@@ -317,7 +307,7 @@
"metadata": {},
"outputs": [],
"source": [
- "socket.setsockopt_string(zmq.SUBSCRIBE, '')\n",
+ "socket.setsockopt_string(zmq.SUBSCRIBE, \"\")\n",
"\n",
"msg = socket.recv_string()\n",
"print(msg)"
@@ -366,20 +356,21 @@
"metadata": {},
"outputs": [],
"source": [
- "class MyClass: \n",
+ "class MyClass:\n",
" x = 10\n",
" y = 20\n",
- " \n",
+ "\n",
" def __getattr__(self, name):\n",
- " print(f'gotta set {name}!')\n",
+ " print(f\"gotta set {name}!\")\n",
" setattr(self, name, True)\n",
" return True\n",
"\n",
+ "\n",
"# if not hasattr(self, name):\n",
"# print(f'gotta create {name}!')\n",
"# setattr(self, name, True)\n",
"# return True\n",
- " \n",
+ "\n",
"c = MyClass()"
]
},
diff --git a/test/notebooks/Prototype the ParamManager.ipynb b/test/notebooks/Prototype the ParamManager.ipynb
index f6dafdb..ff1b4c3 100644
--- a/test/notebooks/Prototype the ParamManager.ipynb
+++ b/test/notebooks/Prototype the ParamManager.ipynb
@@ -30,25 +30,15 @@
},
"outputs": [],
"source": [
- "import numpy as np\n",
- "import random\n",
- "import math\n",
- "import numbers\n",
- "from pprint import pprint\n",
- "\n",
- "from qcodes import Station, Instrument, Parameter\n",
+ "from qcodes import Instrument, Station\n",
"from qcodes.utils import validators\n",
"\n",
- "from instrumentserver import setupLogging, servergui\n",
- "from instrumentserver.helpers import getInstrumentMethods, getInstrumentParameters\n",
- "from instrumentserver.serialize import (\n",
- " toDataFrame, saveParamsToFile, loadParamsFromFile,\n",
- " toParamDict,\n",
- ")\n",
- "\n",
"from instrumentserver.gui import widgetDialog\n",
+ "from instrumentserver.gui.instruments import ParameterManagerGui\n",
"from instrumentserver.params import ParameterManager\n",
- "from instrumentserver.gui.instruments import ParameterManagerGui"
+ "from instrumentserver.serialize import (\n",
+ " toDataFrame,\n",
+ ")"
]
},
{
@@ -70,23 +60,23 @@
"source": [
"Instrument.close_all()\n",
"\n",
- "pm = ParameterManager('pm')\n",
+ "pm = ParameterManager(\"pm\")\n",
"station = Station(pm)\n",
"\n",
- "pm.add('sample_name', 'qubit_test-5', vals=validators.Strings())\n",
+ "pm.add(\"sample_name\", \"qubit_test-5\", vals=validators.Strings())\n",
"\n",
- "pm.add('readout.pulse_length', 1000, unit='ns', vals=validators.Ints())\n",
- "pm.add('readout.envelope', 'envelope_file.npz', vals=validators.Strings())\n",
- "pm.add('readout.n_repetitions', 1000, vals=validators.Ints())\n",
- "pm.add('readout.use_envelope', True, vals=validators.Bool())\n",
+ "pm.add(\"readout.pulse_length\", 1000, unit=\"ns\", vals=validators.Ints())\n",
+ "pm.add(\"readout.envelope\", \"envelope_file.npz\", vals=validators.Strings())\n",
+ "pm.add(\"readout.n_repetitions\", 1000, vals=validators.Ints())\n",
+ "pm.add(\"readout.use_envelope\", True, vals=validators.Bool())\n",
"\n",
- "pm.add('qubit.frequency', 5.678e9, unit='Hz', vals=validators.Numbers())\n",
- "pm.add('qubit.pi_pulse.len', 20, unit='ns', vals=validators.Ints())\n",
- "pm.add('qubit.pi_pulse.amp', 126, unit='DAC units', vals=validators.Ints())\n",
+ "pm.add(\"qubit.frequency\", 5.678e9, unit=\"Hz\", vals=validators.Numbers())\n",
+ "pm.add(\"qubit.pi_pulse.len\", 20, unit=\"ns\", vals=validators.Ints())\n",
+ "pm.add(\"qubit.pi_pulse.amp\", 126, unit=\"DAC units\", vals=validators.Ints())\n",
"\n",
- "pm.add('morestuff.a_sequence', [])\n",
- "pm.add('morestuff.a_complex_number', 0+0j, vals=validators.ComplexNumbers())\n",
- "pm.add('morestuff.something.hidden.deep.away', True, vals=validators.Bool())"
+ "pm.add(\"morestuff.a_sequence\", [])\n",
+ "pm.add(\"morestuff.a_complex_number\", 0 + 0j, vals=validators.ComplexNumbers())\n",
+ "pm.add(\"morestuff.something.hidden.deep.away\", True, vals=validators.Bool())"
]
},
{
@@ -97,9 +87,7 @@
},
"outputs": [],
"source": [
- "dialog = widgetDialog(\n",
- " ParameterManagerGui(pm)\n",
- ")"
+ "dialog = widgetDialog(ParameterManagerGui(pm))"
]
},
{
@@ -285,7 +273,7 @@
},
"outputs": [],
"source": [
- "pm.add('morestuff.something.else', 5)\n",
+ "pm.add(\"morestuff.something.else\", 5)\n",
"pm.qubit.pi_pulse.len(40)"
]
},
diff --git a/test/notebooks/Run the station server from a notebook.ipynb b/test/notebooks/Run the station server from a notebook.ipynb
index 31e2114..24679da 100644
--- a/test/notebooks/Run the station server from a notebook.ipynb
+++ b/test/notebooks/Run the station server from a notebook.ipynb
@@ -19,7 +19,7 @@
},
"outputs": [],
"source": [
- "from qcodes import Instrument, Station, Parameter\n",
+ "from qcodes import Instrument, Parameter, Station\n",
"\n",
"from instrumentserver import servergui"
]
@@ -47,16 +47,18 @@
"\n",
"# dummy instrument: VNA\n",
"from instrumentserver.testing.dummy_instruments.rf import ResonatorResponse\n",
- "dummy_vna = ResonatorResponse('dummy_vna')\n",
+ "\n",
+ "dummy_vna = ResonatorResponse(\"dummy_vna\")\n",
"dummy_vna.start_frequency(4.9e9)\n",
"dummy_vna.stop_frequency(5.1e9)\n",
"\n",
"from instrumentserver.testing.dummy_instruments.rf import Generator\n",
- "rf_src = Generator('rf_src')\n",
- "lo_src = Generator('lo_src')\n",
- "qubit_src = Generator('qubit_src')\n",
"\n",
- "current_sample = Parameter('current_sample', set_cmd=None, initial_value='testsample')\n",
+ "rf_src = Generator(\"rf_src\")\n",
+ "lo_src = Generator(\"lo_src\")\n",
+ "qubit_src = Generator(\"qubit_src\")\n",
+ "\n",
+ "current_sample = Parameter(\"current_sample\", set_cmd=None, initial_value=\"testsample\")\n",
"\n",
"station = Station(dummy_vna, current_sample, rf_src, lo_src, qubit_src)\n",
"\n",
diff --git a/test/prototyping/server_admin.py b/test/prototyping/server_admin.py
index 38dabb1..aefbc52 100755
--- a/test/prototyping/server_admin.py
+++ b/test/prototyping/server_admin.py
@@ -1,55 +1,55 @@
# -*- coding: utf-8 -*-
-import logging
-#%% imports
-from qcodes import Instrument
-from instrumentserver.server import *
-from instrumentserver.client import *
+# %% imports
from pprint import pprint
+from qcodes import Instrument
+
+from instrumentserver.client import Client
+from instrumentserver.server import * # noqa: F401,F403
+
# from instrumentserver import log
# log.setupLogging(addStreamHandler=True, streamHandlerLevel=logging.DEBUG)
# logger = log.logger('instrumentserver')
# logger.setLevel(logging.DEBUG)
-#%% shut down the server
+# %% shut down the server
with Client() as cli:
- cli.ask('SHUTDOWN')
+ cli.ask("SHUTDOWN")
-#%% create vna instrument in server
+# %% create vna instrument in server
Instrument.close_all()
ins_cli = Client()
dummy_vna = ins_cli.find_or_create_instrument(
- 'dummy_vna',
- 'instrumentserver.testing.dummy_instruments.rf.ResonatorResponse',
-
+ "dummy_vna",
+ "instrumentserver.testing.dummy_instruments.rf.ResonatorResponse",
)
dummy_multichan = ins_cli.find_or_create_instrument(
- 'dummy_multichan',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule',
+ "dummy_multichan",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
)
pm = ins_cli.find_or_create_instrument(
- 'pm',
- 'instrumentserver.params.ParameterManager',
+ "pm",
+ "instrumentserver.params.ParameterManager",
)
-#%% Close an instrument
+# %% Close an instrument
with Client() as cli:
- cli.close_instrument('dummy_vna')
+ cli.close_instrument("dummy_vna")
-#%% get instruments from server
+# %% get instruments from server
with Client() as cli:
pprint(cli.list_instruments())
-#%% get the snapshot from the station
+# %% get the snapshot from the station
with Client() as cli:
snap = cli.get_snapshot()
pprint(snap)
diff --git a/test/prototyping/testing_parameter_manager.py b/test/prototyping/testing_parameter_manager.py
index 0de676a..9161dc9 100755
--- a/test/prototyping/testing_parameter_manager.py
+++ b/test/prototyping/testing_parameter_manager.py
@@ -1,32 +1,25 @@
# -*- coding: utf-8 -*-
-#%% imports
-import inspect
+# %% imports
-import numpy as np
-from qcodes import Station, Instrument
-from qcodes.utils import validators
-
-from instrumentserver import QtWidgets
+from qcodes import Instrument, Station
+from instrumentserver.client import Client, ProxyInstrument
from instrumentserver.gui import widgetDialog
-from instrumentserver.params import ParameterManager
from instrumentserver.gui.instruments import ParameterManagerGui
-from instrumentserver.client import Client, ProxyInstrument
-
-
+from instrumentserver.params import ParameterManager
-#%% run the PM locally
+# %% run the PM locally
Instrument.close_all()
-pm = ParameterManager('pm')
+pm = ParameterManager("pm")
station = Station()
station.add_component(pm)
dialog = widgetDialog(ParameterManagerGui(pm))
-#%% instantiate PM in the server.
+# %% instantiate PM in the server.
Instrument.close_all()
cli = Client()
-pm2 = ProxyInstrument('pm', cli=cli, remotePath='pm')
-dialog = widgetDialog(ParameterManagerGui(pm2))
\ No newline at end of file
+pm2 = ProxyInstrument("pm", cli=cli, remotePath="pm")
+dialog = widgetDialog(ParameterManagerGui(pm2))
diff --git a/test/pytest/conftest.py b/test/pytest/conftest.py
index 0bb6a84..8abfed9 100644
--- a/test/pytest/conftest.py
+++ b/test/pytest/conftest.py
@@ -1,35 +1,76 @@
-import instrumentserver.testing.dummy_instruments.generic
import pytest # type: ignore[import-not-found]
+import qcodes as qc
-from instrumentserver.server.core import startServer
+from instrumentserver.client.core import BaseClient
from instrumentserver.client.proxy import Client
+from instrumentserver.server.core import startServer
+
+
+@pytest.fixture(autouse=True, scope="module")
+def _close_instruments_between_modules():
+ """Ensure every test module starts with a clean qcodes instrument registry.
+
+ qcodes.Instrument._all_instruments is a class-level weakref dict that
+ persists for the entire pytest session. Instruments left behind by one
+ module (e.g. channel submodules, which qcodes.Instrument.close() does not
+ cascade-close in this qcodes version) cause KeyError collisions when the
+ next module tries to create an instrument with the same name.
+ """
+ yield
+ qc.Instrument.close_all()
+
+@pytest.fixture(scope="session")
+def qapp_session():
+ """Ensure a QApplication exists for the entire test session.
-@pytest.fixture(scope='module')
-def start_server():
+ QThread (used by startServer) requires a running QApplication.
+ pytest-qt provides 'qapp' per-session, but only when qtbot is requested.
+ This fixture guarantees the app exists even for non-GUI tests.
+ """
+ from instrumentserver import QtWidgets
+
+ app = QtWidgets.QApplication.instance()
+ if app is None:
+ app = QtWidgets.QApplication([])
+ return app
+
+
+@pytest.fixture(scope="module")
+def start_server(qapp_session):
server, thread = startServer()
yield server
- thread.quit()
+ # The zmq loop in StationServer blocks on poll(); thread.quit() on its own
+ # won't interrupt it. Send the SAFEWORD so the server shuts itself down,
+ # then wait for the thread's event loop to exit.
+ try:
+ with BaseClient() as shutdown_cli:
+ shutdown_cli.ask(server.SAFEWORD)
+ except Exception:
+ pass
+ thread.wait(5000)
thread.deleteLater()
- thread = None
@pytest.fixture()
def cli(start_server):
cli = Client()
- return cli
+ yield cli
+ cli.disconnect()
@pytest.fixture()
def dummy_instrument(cli):
- dummy = cli.find_or_create_instrument('dummy', 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule')
+ dummy = cli.find_or_create_instrument(
+ "dummy",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
+ )
return cli, dummy
@pytest.fixture()
def param_manager(cli):
- params = cli.find_or_create_instrument('parameter_manager', 'instrumentserver.params.ParameterManager')
+ params = cli.find_or_create_instrument(
+ "parameter_manager", "instrumentserver.params.ParameterManager"
+ )
return cli, params
-
-
-
diff --git a/test/pytest/test_base.py b/test/pytest/test_base.py
new file mode 100644
index 0000000..8bdc51a
--- /dev/null
+++ b/test/pytest/test_base.py
@@ -0,0 +1,163 @@
+"""Unit tests for instrumentserver.base encode/decode and send/recv.
+
+Note: encode/decode in this module is designed to work with blueprint objects
+that implement .toJson() — it is not a general-purpose JSON encoder.
+"""
+
+import time
+
+import pytest
+import zmq
+
+from instrumentserver.base import (
+ decode,
+ encode,
+ recv,
+ recvMultipart,
+ send,
+ sendBroadcast,
+)
+from instrumentserver.blueprints import (
+ Operation,
+ ParameterBroadcastBluePrint,
+ ServerInstruction,
+)
+
+# ---------------------------------------------------------------------------
+# encode / decode (blueprint objects only)
+# ---------------------------------------------------------------------------
+
+
+def test_encode_broadcast_blueprint_returns_string():
+ bp = ParameterBroadcastBluePrint(
+ name="p", action="parameter-update", value=42, unit="V"
+ )
+ result = encode(bp)
+ assert isinstance(result, str)
+
+
+def test_encode_decode_broadcast_blueprint_round_trip():
+ bp = ParameterBroadcastBluePrint(
+ name="p", action="parameter-update", value=42, unit="V"
+ )
+ encoded = encode(bp)
+ decoded = decode(encoded)
+ assert isinstance(decoded, ParameterBroadcastBluePrint)
+ assert decoded.name == "p"
+ assert decoded.value == 42
+ assert decoded.unit == "V"
+ assert decoded.action == "parameter-update"
+
+
+def test_encode_decode_server_instruction_round_trip():
+ instr = ServerInstruction(operation=Operation.get_existing_instruments)
+ encoded = encode(instr)
+ decoded = decode(encoded)
+ assert isinstance(decoded, ServerInstruction)
+ # operation is stored/restored as its string name
+ assert decoded.operation in (
+ Operation.get_existing_instruments,
+ Operation.get_existing_instruments.name,
+ Operation.get_existing_instruments.value,
+ )
+
+
+def test_decode_string_is_returned_as_is():
+ """encode wraps the string; decode should round-trip it."""
+ s = '{"key": "value"}'
+ # encode on a plain string calls to_dict which returns it unchanged,
+ # then json.dumps wraps it as a JSON string
+ encoded = encode(s)
+ decoded = decode(encoded)
+ # decoded is the original string (json.loads unwraps, deserialize_obj tries
+ # to parse it as JSON again and returns the inner dict)
+ assert decoded == {"key": "value"}
+
+
+# ---------------------------------------------------------------------------
+# send / recv (using zmq PAIR sockets with blueprint objects)
+# ---------------------------------------------------------------------------
+
+
+@pytest.fixture
+def zmq_pair():
+ ctx = zmq.Context()
+ s1 = ctx.socket(zmq.PAIR)
+ s2 = ctx.socket(zmq.PAIR)
+ port = s1.bind_to_random_port("tcp://127.0.0.1")
+ s2.connect(f"tcp://127.0.0.1:{port}")
+ for s in (s1, s2):
+ s.setsockopt(zmq.RCVTIMEO, 2000)
+ s.setsockopt(zmq.LINGER, 0)
+ yield s1, s2
+ s1.close()
+ s2.close()
+ ctx.term()
+
+
+def test_send_recv_broadcast_blueprint(zmq_pair):
+ s1, s2 = zmq_pair
+ bp = ParameterBroadcastBluePrint(
+ name="my_p", action="parameter-update", value=7, unit="Hz"
+ )
+ send(s1, bp)
+ result = recv(s2)
+ assert isinstance(result, ParameterBroadcastBluePrint)
+ assert result.name == "my_p"
+ assert result.value == 7
+
+
+def test_send_recv_server_instruction(zmq_pair):
+ s1, s2 = zmq_pair
+ instr = ServerInstruction(operation=Operation.get_existing_instruments)
+ send(s1, instr)
+ result = recv(s2)
+ assert isinstance(result, ServerInstruction)
+ assert result.operation in (
+ Operation.get_existing_instruments,
+ Operation.get_existing_instruments.name,
+ Operation.get_existing_instruments.value,
+ )
+
+
+# ---------------------------------------------------------------------------
+# sendBroadcast / recvMultipart (PUB/SUB)
+# ---------------------------------------------------------------------------
+
+
+@pytest.fixture
+def zmq_pub_sub():
+ ctx = zmq.Context()
+ pub = ctx.socket(zmq.PUB)
+ sub = ctx.socket(zmq.SUB)
+ port = pub.bind_to_random_port("tcp://127.0.0.1")
+ sub.connect(f"tcp://127.0.0.1:{port}")
+ sub.setsockopt_string(zmq.SUBSCRIBE, "")
+ sub.setsockopt(zmq.RCVTIMEO, 2000)
+ pub.setsockopt(zmq.LINGER, 0)
+ sub.setsockopt(zmq.LINGER, 0)
+ time.sleep(0.05)
+ yield pub, sub
+ pub.close()
+ sub.close()
+ ctx.term()
+
+
+def test_sendBroadcast_recvMultipart(zmq_pub_sub):
+ pub, sub = zmq_pub_sub
+ bp = ParameterBroadcastBluePrint(
+ name="my_param", action="parameter-update", value=7, unit="V"
+ )
+ sendBroadcast(pub, "my_param", bp)
+ name, result = recvMultipart(sub)
+ assert name == "my_param"
+ assert isinstance(result, ParameterBroadcastBluePrint)
+ assert result.value == 7
+
+
+def test_sendBroadcast_name_prefix_matches(zmq_pub_sub):
+ pub, sub = zmq_pub_sub
+ bp = ParameterBroadcastBluePrint(name="ins.param", action="parameter-set", value=42)
+ sendBroadcast(pub, "ins.param", bp)
+ name, result = recvMultipart(sub)
+ assert name == "ins.param"
diff --git a/test/pytest/test_basic_functionality.py b/test/pytest/test_basic_functionality.py
index 5215b10..e5be6e9 100644
--- a/test/pytest/test_basic_functionality.py
+++ b/test/pytest/test_basic_functionality.py
@@ -3,20 +3,24 @@
def test_creating_and_accessing_param(param_manager):
cli, params = param_manager
- params.add_parameter(name='my_param', initial_value=123, unit='M')
+ params.add_parameter(name="my_param", initial_value=123, unit="M")
assert params.my_param() == 123
- assert params.my_param.unit == 'M'
+ assert params.my_param.unit == "M"
params.my_param(456)
assert params.my_param() == 456
def test_getting_all_instruments(cli):
- dummy = cli.find_or_create_instrument('dummy',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule')
- params = cli.find_or_create_instrument('parameter_manager', 'instrumentserver.params.ParameterManager')
+ cli.find_or_create_instrument(
+ "dummy",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
+ )
+ cli.find_or_create_instrument(
+ "parameter_manager", "instrumentserver.params.ParameterManager"
+ )
all_ins = cli.list_instruments()
- expected_dict = ['dummy', 'parameter_manager']
+ expected_dict = ["dummy", "parameter_manager"]
assert sorted(all_ins) == sorted(expected_dict)
@@ -24,7 +28,7 @@ def test_calling_instrument_method(dummy_instrument):
cli, ins = dummy_instrument
ins.param0(1)
assert ins.param0() == 1
- ret = ins.test_func(1, 2, 3, 4, c=[5, 6], d=False, more='hello')
+ ret = ins.test_func(1, 2, 3, 4, c=[5, 6], d=False, more="hello")
expected = [1, 2, 3, [5, 6], False, ins.param0()]
assert expected == ret
@@ -32,13 +36,16 @@ def test_calling_instrument_method(dummy_instrument):
def test_closing_instruments(dummy_instrument):
cli, dummy = dummy_instrument
- assert 'dummy' in cli.list_instruments()
+ assert "dummy" in cli.list_instruments()
cli.close_instrument(dummy.name)
- assert 'dummy' not in cli.list_instruments()
+ assert "dummy" not in cli.list_instruments()
def test_sending_and_receiving_arbitrary_objects(cli):
- magnet = cli.find_or_create_instrument(name='magnet', instrument_class='instrumentserver.testing.dummy_instruments.generic.FieldVectorIns')
+ magnet = cli.find_or_create_instrument(
+ name="magnet",
+ instrument_class="instrumentserver.testing.dummy_instruments.generic.FieldVectorIns",
+ )
# Testing receiving arbitrary return from method
field_vector = magnet.get_field()
@@ -60,4 +67,3 @@ def test_sending_and_receiving_arbitrary_objects(cli):
new_field_vector = FieldVector(101, 102, 103)
magnet.set_field(new_field_vector)
assert magnet.get_field().is_equal(new_field_vector)
-
diff --git a/test/pytest/test_client_station.py b/test/pytest/test_client_station.py
new file mode 100644
index 0000000..495ceee
--- /dev/null
+++ b/test/pytest/test_client_station.py
@@ -0,0 +1,153 @@
+"""Tests for ClientStation and ClientStationGui."""
+
+import pytest
+
+from instrumentserver.client.proxy import ClientStation
+
+DUMMY_CLASS = (
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule"
+)
+
+
+# ---------------------------------------------------------------------------
+# ClientStation (no GUI)
+# ---------------------------------------------------------------------------
+
+
+@pytest.fixture(scope="module")
+def client_station(start_server):
+ station = ClientStation(host="localhost", port=5555)
+ yield station
+ station.disconnect()
+
+
+def test_client_station_creates_instruments(client_station):
+ client_station.find_or_create_instrument("cs_dummy", DUMMY_CLASS)
+ assert "cs_dummy" in client_station.instruments
+
+
+def test_client_station_get_parameters(client_station):
+ client_station.find_or_create_instrument("cs_dummy", DUMMY_CLASS)
+ params = client_station.get_parameters()
+ assert isinstance(params, dict)
+ assert "cs_dummy" in params
+
+
+def test_client_station_set_parameters(client_station):
+ ins = client_station.find_or_create_instrument("cs_dummy", DUMMY_CLASS)
+ ins.param0(0)
+ client_station.set_parameters({"cs_dummy": {"param0": 1}})
+ assert ins.param0() == 1
+
+
+def test_client_station_save_load_parameters(tmp_path, client_station):
+ ins = client_station.find_or_create_instrument("cs_dummy", DUMMY_CLASS)
+ ins.param0(1)
+
+ file_path = str(tmp_path / "params.json")
+ client_station.save_parameters(file_path)
+
+ # Mutate the value
+ ins.param0(0)
+ assert ins.param0() == 0
+
+ # Load back
+ client_station.load_parameters(file_path)
+ assert ins.param0() == 1
+
+
+def test_client_station_get_instrument(client_station):
+ client_station.find_or_create_instrument("cs_dummy", DUMMY_CLASS)
+ retrieved = client_station.get_instrument("cs_dummy")
+ assert retrieved is not None
+ assert retrieved.name == "cs_dummy"
+
+
+def test_client_station_subscript_access(client_station):
+ client_station.find_or_create_instrument("cs_dummy", DUMMY_CLASS)
+ assert client_station["cs_dummy"] is client_station.instruments["cs_dummy"]
+
+
+# ---------------------------------------------------------------------------
+# ClientStationGui
+# ---------------------------------------------------------------------------
+
+
+def test_client_station_gui_opens(qtbot, start_server):
+ from instrumentserver.client.application import ClientStationGui
+
+ station = ClientStation(host="localhost", port=5555)
+ window = ClientStationGui(station)
+ qtbot.addWidget(window)
+ try:
+ assert window is not None
+ finally:
+ window.close()
+ station.disconnect()
+
+
+def test_client_station_gui_has_three_tabs(qtbot, start_server):
+ from instrumentserver.client.application import ClientStationGui
+
+ station = ClientStation(host="localhost", port=5555)
+ window = ClientStationGui(station)
+ qtbot.addWidget(window)
+ try:
+ tab_texts = [window.tabs.tabText(i) for i in range(window.tabs.count())]
+ assert "Station" in tab_texts
+ assert "Log" in tab_texts
+ assert "Server" in tab_texts
+ finally:
+ window.close()
+ station.disconnect()
+
+
+def test_client_station_gui_server_widget_shows_host_port(qtbot, start_server):
+ from instrumentserver.client.application import ClientStationGui
+
+ station = ClientStation(host="localhost", port=5555)
+ window = ClientStationGui(station)
+ qtbot.addWidget(window)
+ try:
+ assert window.server_widget.host.text() == "localhost"
+ assert window.server_widget.port.text() == "5555"
+ finally:
+ window.close()
+ station.disconnect()
+
+
+def test_client_station_gui_station_list_populated(qtbot, start_server):
+ from instrumentserver.client.application import ClientStationGui
+
+ station = ClientStation(host="localhost", port=5555)
+ station.find_or_create_instrument("gui_cs_dummy", DUMMY_CLASS)
+ window = ClientStationGui(station)
+ qtbot.addWidget(window)
+ try:
+ assert window.stationList.topLevelItemCount() >= 1
+ finally:
+ window.close()
+ station.disconnect()
+
+
+def test_client_station_gui_open_instrument_tab(qtbot, start_server):
+ from instrumentserver import QtCore
+ from instrumentserver.client.application import ClientStationGui
+ from instrumentserver.gui.instruments import GenericInstrument
+
+ station = ClientStation(host="localhost", port=5555)
+ station.find_or_create_instrument("gui_cs_dummy2", DUMMY_CLASS)
+ window = ClientStationGui(station)
+ qtbot.addWidget(window)
+ try:
+ items = window.stationList.findItems(
+ "gui_cs_dummy2", QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0
+ )
+ assert len(items) > 0
+
+ window.openInstrumentTab(items[0], 0)
+ assert "gui_cs_dummy2" in window.instrumentTabsOpen
+ assert isinstance(window.instrumentTabsOpen["gui_cs_dummy2"], GenericInstrument)
+ finally:
+ window.close()
+ station.disconnect()
diff --git a/test/pytest/test_config.py b/test/pytest/test_config.py
new file mode 100644
index 0000000..fdbe570
--- /dev/null
+++ b/test/pytest/test_config.py
@@ -0,0 +1,318 @@
+"""Tests for instrumentserver.config.loadConfig."""
+
+from pathlib import Path
+
+import pytest
+
+from instrumentserver.config import GUIFIELD, loadConfig
+
+
+def _write_config(tmp_path: Path, content: str) -> Path:
+ p = tmp_path / "config.yml"
+ p.write_text(content)
+ return p
+
+
+# ---------------------------------------------------------------------------
+# Basic parsing
+# ---------------------------------------------------------------------------
+
+
+def test_minimal_config(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule
+""",
+ )
+ path, serverConfig, fullConfig, tempFile, pollingRates, ipAddresses = loadConfig(
+ cfg
+ )
+ tempFile.close()
+
+ assert "my_ins" in serverConfig
+ assert "my_ins" in fullConfig
+ assert pollingRates == {}
+ assert ipAddresses == {}
+ # returned path is a string
+ assert isinstance(path, str)
+
+
+def test_temp_file_is_readable(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+""",
+ )
+ tempFilePath, _, _, tempFile, _, _ = loadConfig(cfg)
+ tempFile.seek(0)
+ content = tempFile.read()
+ assert len(content) > 0
+ tempFile.close()
+
+
+# ---------------------------------------------------------------------------
+# SERVERFIELDS defaults
+# ---------------------------------------------------------------------------
+
+
+def test_initialize_defaults_to_true(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+""",
+ )
+ _, serverConfig, _, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ assert serverConfig["my_ins"]["initialize"] is True
+
+
+def test_initialize_explicit_false(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+ initialize: false
+""",
+ )
+ _, serverConfig, _, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ assert serverConfig["my_ins"]["initialize"] is False
+
+
+def test_initialize_null_raises(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+ initialize:
+""",
+ )
+ with pytest.raises(AttributeError):
+ loadConfig(cfg)
+
+
+# ---------------------------------------------------------------------------
+# GUI field defaults
+# ---------------------------------------------------------------------------
+
+
+def test_gui_defaults_to_generic_instrument(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+""",
+ )
+ _, _, fullConfig, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ assert fullConfig["my_ins"]["gui"]["type"] == GUIFIELD["type"]
+
+
+def test_gui_generic_alias_maps_to_full_path(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+ gui:
+ type: generic
+""",
+ )
+ _, _, fullConfig, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ assert fullConfig["my_ins"]["gui"]["type"] == GUIFIELD["type"]
+
+
+def test_gui_null_raises(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+ gui:
+""",
+ )
+ with pytest.raises(AttributeError):
+ loadConfig(cfg)
+
+
+# ---------------------------------------------------------------------------
+# Error: missing instruments key
+# ---------------------------------------------------------------------------
+
+
+def test_missing_instruments_key_raises(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+my_ins:
+ type: some.Type
+""",
+ )
+ with pytest.raises(AttributeError):
+ loadConfig(cfg)
+
+
+# ---------------------------------------------------------------------------
+# pollingRate
+# ---------------------------------------------------------------------------
+
+
+def test_polling_rate_parsed(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+ pollingRate:
+ param1: 100
+ param2: 200
+""",
+ )
+ _, _, _, tempFile, pollingRates, _ = loadConfig(cfg)
+ tempFile.close()
+ assert pollingRates == {"my_ins.param1": 100, "my_ins.param2": 200}
+
+
+def test_polling_rate_empty_is_ignored(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+ pollingRate:
+""",
+ )
+ _, _, _, tempFile, pollingRates, _ = loadConfig(cfg)
+ tempFile.close()
+ assert pollingRates == {}
+
+
+# ---------------------------------------------------------------------------
+# networking
+# ---------------------------------------------------------------------------
+
+
+def test_networking_parsed(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+networking:
+ externalBroadcast: tcp://192.168.1.1:5556
+ listeningAddress: 192.168.1.1
+""",
+ )
+ _, _, _, tempFile, _, ipAddresses = loadConfig(cfg)
+ tempFile.close()
+ assert ipAddresses["externalBroadcast"] == "tcp://192.168.1.1:5556"
+ assert ipAddresses["listeningAddress"] == "192.168.1.1"
+
+
+def test_no_networking_section_gives_empty_dict(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.Type
+""",
+ )
+ _, _, _, tempFile, _, ipAddresses = loadConfig(cfg)
+ tempFile.close()
+ assert ipAddresses == {}
+
+
+# ---------------------------------------------------------------------------
+# gui_defaults merging
+# ---------------------------------------------------------------------------
+
+
+def test_gui_defaults_default_section(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.module.MyClass
+gui_defaults:
+ __default__:
+ parameters-hide:
+ - IDN
+""",
+ )
+ _, _, fullConfig, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ kwargs = fullConfig["my_ins"]["gui"].get("kwargs", {})
+ assert "parameters-hide" in kwargs
+ assert "IDN" in kwargs["parameters-hide"]
+
+
+def test_gui_defaults_class_section(tmp_path):
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.module.MyClass
+gui_defaults:
+ MyClass:
+ parameters-hide:
+ - power_level
+""",
+ )
+ _, _, fullConfig, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ kwargs = fullConfig["my_ins"]["gui"].get("kwargs", {})
+ assert "parameters-hide" in kwargs
+ assert "power_level" in kwargs["parameters-hide"]
+
+
+def test_gui_defaults_merging_order(tmp_path):
+ """__default__ + class + instance patterns all appear in merged result."""
+ cfg = _write_config(
+ tmp_path,
+ """\
+instruments:
+ my_ins:
+ type: some.module.MyClass
+ gui:
+ kwargs:
+ parameters-hide:
+ - instance_param
+gui_defaults:
+ __default__:
+ parameters-hide:
+ - default_param
+ MyClass:
+ parameters-hide:
+ - class_param
+""",
+ )
+ _, _, fullConfig, tempFile, _, _ = loadConfig(cfg)
+ tempFile.close()
+ hide = fullConfig["my_ins"]["gui"]["kwargs"]["parameters-hide"]
+ assert "default_param" in hide
+ assert "class_param" in hide
+ assert "instance_param" in hide
diff --git a/test/pytest/test_helpers.py b/test/pytest/test_helpers.py
new file mode 100644
index 0000000..bd7a44e
--- /dev/null
+++ b/test/pytest/test_helpers.py
@@ -0,0 +1,208 @@
+import pytest
+
+from instrumentserver.helpers import (
+ flat_to_nested_dict,
+ flatten_dict,
+ is_flat_dict,
+ nestedAttributeFromString,
+ objectClassPath,
+ stringToArgsAndKwargs,
+ typeClassPath,
+)
+
+# ---------------------------------------------------------------------------
+# stringToArgsAndKwargs
+# ---------------------------------------------------------------------------
+
+
+def test_stringToArgsAndKwargs_empty_string():
+ args, kwargs = stringToArgsAndKwargs("")
+ assert args == []
+ assert kwargs == {}
+
+
+def test_stringToArgsAndKwargs_whitespace_only():
+ args, kwargs = stringToArgsAndKwargs(" ")
+ assert args == []
+ assert kwargs == {}
+
+
+@pytest.mark.parametrize(
+ "value, expected_args, expected_kwargs",
+ [
+ ("1, True", [1, True], {}),
+ ("'hello'", ["hello"], {}),
+ ("1, 2, 3", [1, 2, 3], {}),
+ ("x=1, y=2", [], {"x": 1, "y": 2}),
+ ("1, abc=12.3", [1], {"abc": 12.3}),
+ ],
+)
+def test_stringToArgsAndKwargs_valid(value, expected_args, expected_kwargs):
+ args, kwargs = stringToArgsAndKwargs(value)
+ assert args == expected_args
+ assert kwargs == expected_kwargs
+
+
+def test_stringToArgsAndKwargs_bad_kwarg_format():
+ with pytest.raises(ValueError):
+ stringToArgsAndKwargs("a=1=2")
+
+
+def test_stringToArgsAndKwargs_unevaluable_arg():
+ with pytest.raises(ValueError):
+ stringToArgsAndKwargs("undefined_variable_xyz_abc")
+
+
+def test_stringToArgsAndKwargs_unevaluable_kwarg_value():
+ with pytest.raises(ValueError):
+ stringToArgsAndKwargs("x=undefined_variable_xyz_abc")
+
+
+# ---------------------------------------------------------------------------
+# flat_to_nested_dict
+# ---------------------------------------------------------------------------
+
+
+def test_flat_to_nested_dict_already_flat():
+ flat = {"a": 1, "b": 2}
+ result = flat_to_nested_dict(flat)
+ assert result == {"a": 1, "b": 2}
+
+
+def test_flat_to_nested_dict_single_level():
+ flat = {"a.b": 1, "a.c": 2}
+ result = flat_to_nested_dict(flat)
+ assert result == {"a": {"b": 1, "c": 2}}
+
+
+def test_flat_to_nested_dict_multi_level():
+ flat = {"a.b.c": 1, "a.b.d": 2, "x": 3}
+ result = flat_to_nested_dict(flat)
+ assert result == {"a": {"b": {"c": 1, "d": 2}}, "x": 3}
+
+
+def test_flat_to_nested_dict_empty():
+ assert flat_to_nested_dict({}) == {}
+
+
+# ---------------------------------------------------------------------------
+# flatten_dict
+# ---------------------------------------------------------------------------
+
+
+def test_flatten_dict_already_flat():
+ d = {"a": 1, "b": 2}
+ result = flatten_dict(d)
+ assert result == {"a": 1, "b": 2}
+
+
+def test_flatten_dict_nested():
+ nested = {"a": {"b": 1}, "x": 3}
+ result = flatten_dict(nested)
+ assert result == {"a.b": 1, "x": 3}
+
+
+def test_flatten_dict_custom_sep():
+ nested = {"a": {"b": 1}}
+ result = flatten_dict(nested, sep="/")
+ assert result == {"a/b": 1}
+
+
+def test_flatten_dict_round_trip():
+ flat = {"a.b.c": 1, "a.b.d": 2, "x": 3}
+ nested = flat_to_nested_dict(flat)
+ back_to_flat = flatten_dict(nested)
+ assert back_to_flat == flat
+
+
+# ---------------------------------------------------------------------------
+# is_flat_dict
+# ---------------------------------------------------------------------------
+
+
+def test_is_flat_dict_flat():
+ assert is_flat_dict({"a": 1, "b": "hello"}) is True
+
+
+def test_is_flat_dict_nested():
+ assert is_flat_dict({"a": {"b": 1}}) is False
+
+
+def test_is_flat_dict_mixed():
+ assert is_flat_dict({"a": 1, "b": {"c": 2}}) is False
+
+
+def test_is_flat_dict_empty():
+ assert is_flat_dict({}) is True
+
+
+# ---------------------------------------------------------------------------
+# nestedAttributeFromString
+# ---------------------------------------------------------------------------
+
+
+class _Root:
+ class _Child:
+ value = 42
+
+ scalar = 99
+
+
+def test_nestedAttributeFromString_single_level():
+ root = _Root()
+ assert nestedAttributeFromString(root, "scalar") == 99
+
+
+def test_nestedAttributeFromString_two_levels():
+ root = _Root()
+ assert nestedAttributeFromString(root, "_Child.value") == 42
+
+
+def test_nestedAttributeFromString_missing_raises():
+ root = _Root()
+ with pytest.raises(AttributeError):
+ nestedAttributeFromString(root, "nonexistent_attr")
+
+
+def test_nestedAttributeFromString_nested_missing_raises():
+ root = _Root()
+ with pytest.raises(AttributeError):
+ nestedAttributeFromString(root, "_Child.nonexistent")
+
+
+# ---------------------------------------------------------------------------
+# typeClassPath / objectClassPath
+# ---------------------------------------------------------------------------
+
+
+class _MyClass:
+ pass
+
+
+def test_typeClassPath_contains_class_name():
+ path = typeClassPath(_MyClass)
+ assert "_MyClass" in path
+ assert "." in path
+
+
+def test_objectClassPath_contains_class_name():
+ obj = _MyClass()
+ path = objectClassPath(obj)
+ assert "_MyClass" in path
+ assert "." in path
+
+
+def test_typeClassPath_builtin():
+ path = typeClassPath(int)
+ assert "int" in path
+
+
+def test_objectClassPath_builtin_instance():
+ path = objectClassPath(42)
+ assert "int" in path
+
+
+def test_typeClassPath_and_objectClassPath_agree():
+ """typeClassPath on the class and objectClassPath on an instance should match."""
+ obj = _MyClass()
+ assert typeClassPath(_MyClass) == objectClassPath(obj)
diff --git a/test/pytest/test_json_serializable.py b/test/pytest/test_json_serializable.py
index 011e782..1916b57 100644
--- a/test/pytest/test_json_serializable.py
+++ b/test/pytest/test_json_serializable.py
@@ -2,21 +2,23 @@
import qcodes as qc
from qcodes.math_utils.field_vector import FieldVector
-from instrumentserver.blueprints import (bluePrintFromInstrumentModule,
- bluePrintFromMethod,
- bluePrintFromParameter,
- bluePrintToDict,
- deserialize_obj,
- ParameterBroadcastBluePrint,
- iterable_to_serialized_dict,
- dict_to_serialized_dict, to_dict,
- )
-from instrumentserver.testing.dummy_instruments.generic import DummyInstrumentWithSubmodule
+from instrumentserver.blueprints import (
+ ParameterBroadcastBluePrint,
+ bluePrintFromInstrumentModule,
+ bluePrintFromMethod,
+ bluePrintFromParameter,
+ bluePrintToDict,
+ deserialize_obj,
+ dict_to_serialized_dict,
+ iterable_to_serialized_dict,
+)
+from instrumentserver.testing.dummy_instruments.generic import (
+ DummyInstrumentWithSubmodule,
+)
from instrumentserver.testing.dummy_instruments.rf import ResonatorResponse
class CustomParameter(qc.Parameter):
-
def __int__(self, name, *args, **kwargs):
"""
Well lets see if you go anywhere
@@ -33,23 +35,21 @@ def set_raw(self, val):
class MyClass:
-
- attributes = ['x', 'y', 'z']
+ attributes = ["x", "y", "z"]
def __init__(self, x=1, y=2, z=3):
self.x = x
self.y = y
self.z = z
-
- def customFunction(self, x: int, y:int) -> int:
- print(f'I am in my function')
- return x*y
+ def customFunction(self, x: int, y: int) -> int:
+ print("I am in my function")
+ return x * y
def test_basic_param_dictionary():
- my_param = CustomParameter(name='my_param', unit='M')
- param_bp = bluePrintFromParameter('', my_param)
+ my_param = CustomParameter(name="my_param", unit="M")
+ param_bp = bluePrintFromParameter("", my_param)
bp_dict = bluePrintToDict(param_bp)
reconstructed_bp = deserialize_obj(bp_dict)
assert param_bp == reconstructed_bp
@@ -64,13 +64,13 @@ def test_basic_function_dictionary():
def test_basic_instrument_dictionary():
- my_rr = ResonatorResponse('rr')
+ my_rr = ResonatorResponse("rr")
instrument_bp = bluePrintFromInstrumentModule("", my_rr)
bp_dict = bluePrintToDict(instrument_bp)
reconstructed_bp = deserialize_obj(bp_dict)
assert instrument_bp == reconstructed_bp
- my_dummy = DummyInstrumentWithSubmodule('dummy')
+ my_dummy = DummyInstrumentWithSubmodule("dummy")
dummy_bp = bluePrintFromInstrumentModule("", my_dummy)
dummy_bp_dict = bluePrintToDict(dummy_bp)
reconstructed_dummy_bp = deserialize_obj(dummy_bp_dict)
@@ -78,7 +78,9 @@ def test_basic_instrument_dictionary():
def test_basic_broadcast_parameter_dictionary():
- broadcast_bp = ParameterBroadcastBluePrint(name='my_param', action='an_action', value=-56, unit='M')
+ broadcast_bp = ParameterBroadcastBluePrint(
+ name="my_param", action="an_action", value=-56, unit="M"
+ )
bp_dict = bluePrintToDict(broadcast_bp)
reconstructed_bp = deserialize_obj(bp_dict)
assert broadcast_bp == reconstructed_bp
@@ -88,20 +90,35 @@ def test_arbitrary_class_serialization():
arbitrary_class_1 = MyClass()
arbitrary_class_2 = MyClass(x=10, y=11, z=12)
- expected_arg = [{'x': 1, 'y': 2, 'z': 3,
- '_class_type': f'{arbitrary_class_1.__module__}.{arbitrary_class_1.__class__.__name__}'}]
- expected_kwargs = {'arbitrary_class_2': {'x': 10, 'y': 11, 'z': 12,
- '_class_type': f'{arbitrary_class_2.__module__}.{arbitrary_class_2.__class__.__name__}'}}
+ expected_arg = [
+ {
+ "x": 1,
+ "y": 2,
+ "z": 3,
+ "_class_type": f"{arbitrary_class_1.__module__}.{arbitrary_class_1.__class__.__name__}",
+ }
+ ]
+ expected_kwargs = {
+ "arbitrary_class_2": {
+ "x": 10,
+ "y": 11,
+ "z": 12,
+ "_class_type": f"{arbitrary_class_2.__module__}.{arbitrary_class_2.__class__.__name__}",
+ }
+ }
returned_args = iterable_to_serialized_dict([arbitrary_class_1])
- returned_kwargs = dict_to_serialized_dict({'arbitrary_class_2': arbitrary_class_2})
+ returned_kwargs = dict_to_serialized_dict({"arbitrary_class_2": arbitrary_class_2})
assert returned_args == expected_arg
assert expected_kwargs == returned_kwargs
def test_send_arbitrary_objects(cli):
- field_vector_ins = cli.find_or_create_instrument('field_vector', instrument_class="instrumentserver.testing.dummy_instruments.generic.FieldVectorIns")
+ field_vector_ins = cli.find_or_create_instrument(
+ "field_vector",
+ instrument_class="instrumentserver.testing.dummy_instruments.generic.FieldVectorIns",
+ )
new_vector = FieldVector(x=12.0, y=12.0, z=12.0)
field_vector_ins.set_field(new_vector)
@@ -111,8 +128,10 @@ def test_send_arbitrary_objects(cli):
def test_sending_complex_numbers(cli):
- field_vector_ins = cli.find_or_create_instrument('field_vector',
- instrument_class="instrumentserver.testing.dummy_instruments.generic.FieldVectorIns")
+ field_vector_ins = cli.find_or_create_instrument(
+ "field_vector",
+ instrument_class="instrumentserver.testing.dummy_instruments.generic.FieldVectorIns",
+ )
# Getting value
expected_complex = 1 + 1j
diff --git a/test/pytest/test_param_manager.py b/test/pytest/test_param_manager.py
index e102607..67b168b 100644
--- a/test/pytest/test_param_manager.py
+++ b/test/pytest/test_param_manager.py
@@ -5,39 +5,58 @@
def prep_param_manager(params, template=1):
if template == 1:
- params.add_parameter(name='my_param', initial_value=123, unit='M')
- params.add_parameter(name='nested_param.child1', initial_value=456, unit='a')
- params.add_parameter(name='nested_param.child2', initial_value=789, unit='b')
- params.add_parameter(name='nested_param.how.are.you', initial_value=111, unit='b')
- params.add_parameter(name='nested_param.how.are.too', initial_value=222, unit='b')
+ params.add_parameter(name="my_param", initial_value=123, unit="M")
+ params.add_parameter(name="nested_param.child1", initial_value=456, unit="a")
+ params.add_parameter(name="nested_param.child2", initial_value=789, unit="b")
+ params.add_parameter(
+ name="nested_param.how.are.you", initial_value=111, unit="b"
+ )
+ params.add_parameter(
+ name="nested_param.how.are.too", initial_value=222, unit="b"
+ )
elif template == 2:
- params.add_parameter(name='his_param', initial_value=-123, unit='n')
- params.add_parameter(name='nested_param.son1', initial_value=-456, unit='c')
- params.add_parameter(name='nested_param.son2', initial_value=-789, unit='d')
+ params.add_parameter(name="his_param", initial_value=-123, unit="n")
+ params.add_parameter(name="nested_param.son1", initial_value=-456, unit="c")
+ params.add_parameter(name="nested_param.son2", initial_value=-789, unit="d")
elif template == 3:
- params.add_parameter(name='her_param', initial_value=0, unit='p')
- params.add_parameter(name='nested_param.daughter1', initial_value=1, unit='e')
- params.add_parameter(name='nested_param.daughter2', initial_value=2, unit='f')
+ params.add_parameter(name="her_param", initial_value=0, unit="p")
+ params.add_parameter(name="nested_param.daughter1", initial_value=1, unit="e")
+ params.add_parameter(name="nested_param.daughter2", initial_value=2, unit="f")
elif template == 4:
- params.add_parameter(name='our_param', initial_value=3, unit='m')
- params.add_parameter(name='nested_param.sibling1', initial_value=[4, 6], unit='g')
+ params.add_parameter(name="our_param", initial_value=3, unit="m")
+ params.add_parameter(
+ name="nested_param.sibling1", initial_value=[4, 6], unit="g"
+ )
params.nested_param.sibling1(1234856834)
- params.add_parameter(name='nested_param.sibling2', initial_value=[5, 7], unit='h')
+ params.add_parameter(
+ name="nested_param.sibling2", initial_value=[5, 7], unit="h"
+ )
def test_param(param_manager):
cli, params = param_manager
- params.add_parameter(name='my_param', initial_value=123, unit='M')
+ params.add_parameter(name="my_param", initial_value=123, unit="M")
assert params.my_param() == 123
- assert params.my_param.unit == 'M'
+ assert params.my_param.unit == "M"
params.my_param(456)
assert params.my_param() == 456
+def test_proxy_add_remove_parameter(param_manager):
+ cli, params = param_manager
+
+ params.add_parameter(name="probe_param", initial_value=42, unit="V")
+ assert "probe_param" in params.parameters
+ assert params.probe_param() == 42
+
+ params.remove_parameter(name="probe_param")
+ assert "probe_param" not in params.parameters
+
+
def test_removing_all_params():
- params = ParameterManager(name='params')
+ params = ParameterManager(name="params")
prep_param_manager(params)
prep_param_manager(params, template=2)
@@ -50,37 +69,41 @@ def test_removing_all_params():
def test_finding_all_profiles(tmp_path):
- params = ParameterManager(name='params')
+ params = ParameterManager(name="params")
prep_param_manager(params)
- params.toFile(tmp_path, 'first')
+ params.toFile(tmp_path, "first")
prep_param_manager(params, template=2)
- params.toFile(tmp_path, 'second')
+ params.toFile(tmp_path, "second")
prep_param_manager(params, template=3)
- params.toFile(tmp_path, 'third')
+ params.toFile(tmp_path, "third")
prep_param_manager(params, template=4)
- params.toFile(tmp_path, 'fourth')
+ params.toFile(tmp_path, "fourth")
params.workingDirectory = tmp_path
profiles = params.refresh_profiles()
- assert sorted(profiles) == sorted(['parameter_manager-first.json',
- 'parameter_manager-second.json',
- 'parameter_manager-third.json',
- 'parameter_manager-fourth.json'])
+ assert sorted(profiles) == sorted(
+ [
+ "parameter_manager-first.json",
+ "parameter_manager-second.json",
+ "parameter_manager-third.json",
+ "parameter_manager-fourth.json",
+ ]
+ )
def test_saving_correct_profile(tmp_path):
- params = ParameterManager(name='params')
+ params = ParameterManager(name="params")
params.workingDirectory = tmp_path
prep_param_manager(params)
- params.toFile(name='first')
- file_path = tmp_path.joinpath('parameter_manager-first.json')
+ params.toFile(name="first")
+ file_path = tmp_path.joinpath("parameter_manager-first.json")
assert file_path.exists()
params.my_param(8888)
@@ -89,24 +112,24 @@ def test_saving_correct_profile(tmp_path):
with open(file_path) as file:
data = json.load(file)
- assert data['params.my_param']['value'] == 8888
+ assert data["params.my_param"]["value"] == 8888
def test_loading_correct_profile(tmp_path):
- params = ParameterManager(name='params')
+ params = ParameterManager(name="params")
params.workingDirectory = tmp_path
prep_param_manager(params)
- params.toFile(name='first')
- file_path = tmp_path.joinpath('parameter_manager-first.json')
+ params.toFile(name="first")
+ file_path = tmp_path.joinpath("parameter_manager-first.json")
with open(file_path) as file:
data = json.load(file)
- data['params.my_param']['value'] = 9999
+ data["params.my_param"]["value"] = 9999
- with open(file_path, 'w') as file:
+ with open(file_path, "w") as file:
json.dump(data, file)
params.fromFile()
@@ -114,15 +137,15 @@ def test_loading_correct_profile(tmp_path):
def prep_switching_profiles(tmp_path):
- params = ParameterManager(name='params')
+ params = ParameterManager(name="params")
prep_param_manager(params)
- params.toFile(tmp_path, 'first')
+ params.toFile(tmp_path, "first")
params.remove_all_parameters()
prep_param_manager(params, template=2)
- params.toFile(tmp_path, 'second')
+ params.toFile(tmp_path, "second")
params.workingDirectory = tmp_path
params.refresh_profiles()
@@ -134,7 +157,7 @@ def test_switching_profiles(tmp_path):
params = prep_switching_profiles(tmp_path)
- params.switch_to_profile('parameter_manager-first.json')
+ params.switch_to_profile("parameter_manager-first.json")
assert params.my_param() == 123
assert params.nested_param.child1() == 456
@@ -144,13 +167,13 @@ def test_switching_profiles(tmp_path):
def test_switching_profiles_short_name(tmp_path):
params = prep_switching_profiles(tmp_path)
- params.switch_to_profile('first')
+ params.switch_to_profile("first")
assert params.my_param() == 123
assert params.nested_param.child1() == 456
assert params.nested_param.child2() == 789
- params.switch_to_profile('second')
+ params.switch_to_profile("second")
assert params.his_param() == -123
assert params.nested_param.son1() == -456
@@ -164,32 +187,31 @@ def test_switching_profiles_automatic_save(tmp_path):
params.nested_param.son1(222)
params.nested_param.son2(333)
- params.switch_to_profile('first')
+ params.switch_to_profile("first")
- with open(tmp_path.joinpath('parameter_manager-second.json')) as file:
+ with open(tmp_path.joinpath("parameter_manager-second.json")) as file:
second = json.load(file)
- assert second['params.his_param']['value'] == 111
- assert second['params.nested_param.son1']['value'] == 222
- assert second['params.nested_param.son2']['value'] == 333
+ assert second["params.his_param"]["value"] == 111
+ assert second["params.nested_param.son1"]["value"] == 222
+ assert second["params.nested_param.son2"]["value"] == 333
def test_selectedProfile_only_changing_when_correct_name(tmp_path):
- params = ParameterManager(name='params')
+ params = ParameterManager(name="params")
prep_param_manager(params)
- names_path = tmp_path.joinpath('names.json')
+ names_path = tmp_path.joinpath("names.json")
params.toFile(names_path)
- assert params.selectedProfile == 'parameter_manager-params.json'
+ assert params.selectedProfile == "parameter_manager-params.json"
params.fromFile(names_path)
- assert params.selectedProfile == 'parameter_manager-params.json'
+ assert params.selectedProfile == "parameter_manager-params.json"
- params.toFile(tmp_path, 'params')
- assert params.selectedProfile == 'parameter_manager-params.json'
+ params.toFile(tmp_path, "params")
+ assert params.selectedProfile == "parameter_manager-params.json"
- new_path = names_path.replace(tmp_path.joinpath('parameter_manager-names.json'))
+ new_path = names_path.replace(tmp_path.joinpath("parameter_manager-names.json"))
params.fromFile(new_path)
- assert params.selectedProfile == 'parameter_manager-names.json'
-
+ assert params.selectedProfile == "parameter_manager-names.json"
diff --git a/test/pytest/test_serialize.py b/test/pytest/test_serialize.py
index 9ab5e5e..05f357e 100644
--- a/test/pytest/test_serialize.py
+++ b/test/pytest/test_serialize.py
@@ -1,4 +1,5 @@
-from qcodes import Station, Instrument, Parameter
+from qcodes import Parameter, Station
+
from instrumentserver.serialize import toParamDict
@@ -6,7 +7,7 @@ def test_toParamDict_paramsBasic():
"""Test serializing a few parameters added to a station"""
paramNames = [f"parameter_{i}" for i in range(4)]
- paramValues = [123, None, True, 'abcdef']
+ paramValues = [123, None, True, "abcdef"]
params = []
for n, v in zip(paramNames, paramValues):
@@ -21,4 +22,3 @@ def test_toParamDict_paramsBasic():
paramDict_expt[n] = v
assert paramDict_test == paramDict_expt
-
diff --git a/test/pytest/test_server_gui.py b/test/pytest/test_server_gui.py
index 849c05f..435e0cb 100644
--- a/test/pytest/test_server_gui.py
+++ b/test/pytest/test_server_gui.py
@@ -3,9 +3,25 @@
from instrumentserver import QtCore
from instrumentserver.gui.instruments import GenericInstrument
+from instrumentserver.helpers import flatten_dict
from instrumentserver.server.application import startServerGuiApplication
+def _shutdown_server_window(window):
+ """Trigger closeEvent and wait for the server thread to exit so the next
+ test can bind the same port again."""
+ try:
+ window.close()
+ except Exception:
+ pass
+ thread = getattr(window, "stationServerThread", None)
+ if thread is not None:
+ try:
+ thread.wait(5000)
+ except Exception:
+ pass
+
+
def test_saving_button(qtbot):
correct_file_dict = {
"rr.bandwidth": 10000.0,
@@ -18,13 +34,13 @@ def test_saving_button(qtbot):
"rr.resonator_frequency": 5000000000.0,
"rr.resonator_linewidth": 1000000.0,
"rr.start_frequency": 20000.0,
- "rr.stop_frequency": 20000000000.0
+ "rr.stop_frequency": 20000000000.0,
}
-
window = startServerGuiApplication()
- rr = window.client.find_or_create_instrument('rr',
- 'instrumentserver.testing.dummy_instruments.rf.ResonatorResponse')
+ window.client.find_or_create_instrument(
+ "rr", "instrumentserver.testing.dummy_instruments.rf.ResonatorResponse"
+ )
qtbot.addWidget(window)
saving_widget = window.toolBar.widgetForAction(window.saveParamsAction)
@@ -33,12 +49,13 @@ def test_saving_button(qtbot):
file_path = Path(window._paramValuesFile)
try:
assert file_path.is_file()
- with open(str(file_path), 'r') as f:
+ with open(str(file_path), "r") as f:
loaded_file = json.load(f)
- assert correct_file_dict == loaded_file
+ assert correct_file_dict == flatten_dict(loaded_file)
finally:
file_path.unlink(missing_ok=True)
+ _shutdown_server_window(window)
def test_loading_button(qtbot):
@@ -57,11 +74,13 @@ def test_loading_button(qtbot):
file_path = Path(window._paramValuesFile)
- with open(str(file_path), 'w+') as f:
+ with open(str(file_path), "w+") as f:
json.dump(correct_file_dict, f)
- dummy = window.client.find_or_create_instrument('dummy',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule')
+ dummy = window.client.find_or_create_instrument(
+ "dummy",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
+ )
qtbot.addWidget(window)
loading_widget = window.toolBar.widgetForAction(window.loadParamsAction)
@@ -72,21 +91,26 @@ def test_loading_button(qtbot):
finally:
file_path.unlink(missing_ok=True)
+ _shutdown_server_window(window)
def test_refresh_button(qtbot):
window = startServerGuiApplication()
qtbot.addWidget(window)
+ try:
+ assert window.stationList.topLevelItemCount() == 0
- assert window.stationList.topLevelItemCount() == 0
-
- dummy = window.client.find_or_create_instrument('dummy',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule')
+ window.client.find_or_create_instrument(
+ "dummy",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
+ )
- refresh_widget = window.toolBar.widgetForAction(window.refreshStationAction)
- qtbot.mouseClick(refresh_widget, QtCore.Qt.LeftButton)
+ refresh_widget = window.toolBar.widgetForAction(window.refreshStationAction)
+ qtbot.mouseClick(refresh_widget, QtCore.Qt.LeftButton)
- assert window.stationList.topLevelItemCount() == 1
+ assert window.stationList.topLevelItemCount() == 1
+ finally:
+ _shutdown_server_window(window)
def test_clicking_an_item(qtbot):
@@ -94,36 +118,45 @@ def test_clicking_an_item(qtbot):
window = startServerGuiApplication()
qtbot.addWidget(window)
+ try:
+ assert window.stationList.topLevelItemCount() == 0
- assert window.stationList.topLevelItemCount() == 0
-
- dummy = window.client.find_or_create_instrument('dummy',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule')
+ window.client.find_or_create_instrument(
+ "dummy",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
+ )
- window.refreshStationAction.trigger()
- item = window.stationList.findItems('dummy', QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
- widget = item[0].treeWidget()
- qtbot.mouseClick(widget, QtCore.Qt.LeftButton)
+ window.refreshStationAction.trigger()
+ item = window.stationList.findItems(
+ "dummy", QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0
+ )
+ widget = item[0].treeWidget()
+ qtbot.mouseClick(widget, QtCore.Qt.LeftButton)
- assert True
+ assert True
+ finally:
+ _shutdown_server_window(window)
def test_opening_new_tab_generic_object(qtbot):
window = startServerGuiApplication()
qtbot.addWidget(window)
+ try:
+ window.client.find_or_create_instrument(
+ "dummy",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule",
+ )
- dummy = window.client.find_or_create_instrument('dummy',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentWithSubmodule')
-
- window.refreshStationAction.trigger()
- item = window.stationList.findItems('dummy', QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0)
-
- # Manually triggering the tab opening since qtbot refuses to double-click an item
- window.addInstrumentTab(item[0], 0)
-
- assert 'dummy' in window.instrumentTabsOpen
-
- assert isinstance(window.instrumentTabsOpen['dummy'], GenericInstrument)
+ window.refreshStationAction.trigger()
+ item = window.stationList.findItems(
+ "dummy", QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive, 0
+ )
+ # Manually triggering the tab opening since qtbot refuses to double-click an item
+ window.addInstrumentTab(item[0], 0)
+ assert "dummy" in window.instrumentTabsOpen
+ assert isinstance(window.instrumentTabsOpen["dummy"], GenericInstrument)
+ finally:
+ _shutdown_server_window(window)
diff --git a/test/test_async_requests/client_station_gui.py b/test/test_async_requests/client_station_gui.py
index 013d9c5..490704b 100644
--- a/test/test_async_requests/client_station_gui.py
+++ b/test/test_async_requests/client_station_gui.py
@@ -1,9 +1,10 @@
import sys
from pathlib import Path
+
+from instrumentserver import QtWidgets
from instrumentserver.client import ClientStation
from instrumentserver.client.application import ClientStationGui
-from instrumentserver import QtWidgets
if __name__ == "__main__":
# gather some test config files
@@ -11,16 +12,16 @@
# create client station
app = QtWidgets.QApplication(sys.argv)
- cli_station = ClientStation(host="localhost", config_path=config_path)#, param_path=param_path, port=5555)
-
+ cli_station = ClientStation(
+ host="localhost", config_path=config_path
+ ) # , param_path=param_path, port=5555)
+
# test client station functions
print(cli_station.get_parameters()["test1"]["param1"])
test1 = cli_station["test1"]
print(test1.get_random())
-
+
# make and display gui window
win = ClientStationGui(cli_station)
win.show()
sys.exit(app.exec_())
-
-
diff --git a/test/test_async_requests/demo_concurrency.py b/test/test_async_requests/demo_concurrency.py
index d8a1d2b..e2080d9 100644
--- a/test/test_async_requests/demo_concurrency.py
+++ b/test/test_async_requests/demo_concurrency.py
@@ -1,8 +1,9 @@
-from instrumentserver.client import Client
import sys
import time
-'''
+from instrumentserver.client import Client
+
+"""
Simple concurrency demo.
Usage (server already running):
@@ -23,7 +24,7 @@
This mimics the case when one client is ramping bias voltage, while another client wants to change a parameter of
a different instrument. Or more commonly, a client is ramping bias voltage, and we want to view parameter of an instrument
in the server gui (which also is basically another client that runs in a different thread.)
-'''
+"""
if __name__ == "__main__":
role = sys.argv[1] if len(sys.argv) > 1 else "ramp"
@@ -43,17 +44,21 @@
t0 = time.time()
- if role == "ramp": # within a single process, operations are always blocking
+ if role == "ramp": # within a single process, operations are always blocking
print("[ramp] dummy1.get_random_timeout(10)")
print(dummy1.get_random_timeout(10))
print("[after ramp] dummy2.get_random()")
print(dummy2.get_random())
- elif role == "same": # from a different process, operations on the same instrument are still blocked
+ elif (
+ role == "same"
+ ): # from a different process, operations on the same instrument are still blocked
print("[same] dummy1.get_random() (same instrument as ramp)")
print(dummy1.get_random())
- elif role == "other": # from a different process, operations on a different instrument are NOT blocked
+ elif (
+ role == "other"
+ ): # from a different process, operations on a different instrument are NOT blocked
print("[other] dummy2.get_random() (different instrument)")
print(dummy2.get_random())
@@ -61,5 +66,3 @@
print(f"Unknown role {role!r}. Use 'ramp', 'same', or 'other'.")
print(f"[{role}] took {time.time() - t0:.3f} s")
-
-
diff --git a/test/test_async_requests/test_client.py b/test/test_async_requests/test_client.py
index d41f2ac..1509faf 100644
--- a/test/test_async_requests/test_client.py
+++ b/test/test_async_requests/test_client.py
@@ -1,28 +1,30 @@
from instrumentserver.client import Client
-
-'''
+"""
A simple script for testing the new features on the server/client.
-'''
+"""
if __name__ == "__main__":
cli = Client(timeout=15000, port=5555)
import time
+
t0 = time.time()
- dummy1 = cli.find_or_create_instrument('test1',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentTimeout')
- dummy2 = cli.find_or_create_instrument('test2',
- 'instrumentserver.testing.dummy_instruments.generic.DummyInstrumentTimeout')
-
+ dummy1 = cli.find_or_create_instrument(
+ "test1",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentTimeout",
+ )
+ dummy2 = cli.find_or_create_instrument(
+ "test2",
+ "instrumentserver.testing.dummy_instruments.generic.DummyInstrumentTimeout",
+ )
+
# print(dummy1.get_random_timeout(10))
print(dummy2.get_random())
dummy1.param2(1e9)
-
+
# for i in range(20):
# print(dummy1.get_random())
# print(dummy2.get_random())
-
- print(f"took {time.time() - t0} seconds")
-
+ print(f"took {time.time() - t0} seconds")
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 0000000..16648aa
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,3123 @@
+version = 1
+revision = 3
+requires-python = ">=3.11"
+resolution-markers = [
+ "python_full_version >= '3.15' and sys_platform == 'win32'",
+ "python_full_version == '3.14.*' and sys_platform == 'win32'",
+ "python_full_version >= '3.15' and sys_platform == 'emscripten'",
+ "python_full_version == '3.14.*' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'",
+ "python_full_version < '3.12' and sys_platform == 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'",
+ "python_full_version < '3.12' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+
+[[package]]
+name = "accessible-pygments"
+version = "0.0.5"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bc/c1/bbac6a50d02774f91572938964c582fff4270eee73ab822a4aeea4d8b11b/accessible_pygments-0.0.5.tar.gz", hash = "sha256:40918d3e6a2b619ad424cb91e556bd3bd8865443d9f22f1dcdf79e33c8046872", size = 1377899, upload-time = "2024-05-10T11:23:10.216Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903, upload-time = "2024-05-10T11:23:08.421Z" },
+]
+
+[[package]]
+name = "alabaster"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a6/f8/d9c74d0daf3f742840fd818d69cfae176fa332022fd44e3469487d5a9420/alabaster-1.0.0.tar.gz", hash = "sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e", size = 24210, upload-time = "2024-07-26T18:15:03.762Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/b3/6b4067be973ae96ba0d615946e314c5ae35f9f993eca561b356540bb0c2b/alabaster-1.0.0-py3-none-any.whl", hash = "sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b", size = 13929, upload-time = "2024-07-26T18:15:02.05Z" },
+]
+
+[[package]]
+name = "appnope"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
+]
+
+[[package]]
+name = "asttokens"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "babel"
+version = "2.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" },
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.14.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "soupsieve" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" },
+]
+
+[[package]]
+name = "bleach"
+version = "6.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" },
+]
+
+[package.optional-dependencies]
+css = [
+ { name = "tinycss2" },
+]
+
+[[package]]
+name = "broadbean"
+version = "0.14.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "matplotlib" },
+ { name = "numpy" },
+ { name = "schema" },
+ { name = "versioningit" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/38/c9/c64ec69941544561f503fe092f2f32b9117252b2dd22b90368787d2316a2/broadbean-0.14.0.tar.gz", hash = "sha256:bfe3afea69529da246f7ca2803d0213c625f96b15a7ca4283b9c22f8fc5c655c", size = 44803, upload-time = "2024-03-06T22:15:44.076Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/4d/2b3b4b35456176182d45cdf977fdea80bf71be563f4074536ec3436eed9c/broadbean-0.14.0-py3-none-any.whl", hash = "sha256:7a9195ef16241853e2ea20aedc6f67ee72f5464a463b3584fcbedcb63daf88e7", size = 36755, upload-time = "2024-03-06T22:15:42.41Z" },
+]
+
+[[package]]
+name = "certifi"
+version = "2026.4.22"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" },
+]
+
+[[package]]
+name = "cf-xarray"
+version = "0.10.11"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "xarray" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e3/78/f4f38e7ea6221773ea48d85c00d529b1fdc7378a1a1b77c2b77661446a0b/cf_xarray-0.10.11.tar.gz", hash = "sha256:e10ee37b0ed3ba36f42346360f2bc070c690ddc73bb9dcdd9463b3a221453be3", size = 686693, upload-time = "2026-02-03T19:17:42.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/65/ce/5c4f4660da5521d90bea62cdf8396d7e4ce4a00513e218d267b97f9ea453/cf_xarray-0.10.11-py3-none-any.whl", hash = "sha256:c47fff625766c69a66fedef368d9787acb0819b32d8bd022f8b045089b42109a", size = 78421, upload-time = "2026-02-03T19:17:40.431Z" },
+]
+
+[[package]]
+name = "cffi"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pycparser", marker = "implementation_name != 'PyPy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" },
+ { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" },
+ { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" },
+ { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" },
+ { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" },
+ { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" },
+ { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" },
+ { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" },
+ { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" },
+ { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" },
+ { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" },
+ { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" },
+ { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" },
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.7"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/d7/b5b7020a0565c2e9fa8c09f4b5fa6232feb326b8c20081ccded47ea368fd/charset_normalizer-3.4.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7641bb8895e77f921102f72833904dcd9901df5d6d72a2ab8f31d04b7e51e4e7", size = 309705, upload-time = "2026-04-02T09:26:02.191Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/53/58c29116c340e5456724ecd2fff4196d236b98f3da97b404bc5e51ac3493/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:202389074300232baeb53ae2569a60901f7efadd4245cf3a3bf0617d60b439d7", size = 206419, upload-time = "2026-04-02T09:26:03.583Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/02/e8146dc6591a37a00e5144c63f29fb7c97a734ea8a111190783c0e60ab63/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:30b8d1d8c52a48c2c5690e152c169b673487a2a58de1ec7393196753063fcd5e", size = 227901, upload-time = "2026-04-02T09:26:04.738Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/73/77486c4cd58f1267bf17db420e930c9afa1b3be3fe8c8b8ebbebc9624359/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:532bc9bf33a68613fd7d65e4b1c71a6a38d7d42604ecf239c77392e9b4e8998c", size = 222742, upload-time = "2026-04-02T09:26:06.36Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/fa/f74eb381a7d94ded44739e9d94de18dc5edc9c17fb8c11f0a6890696c0a9/charset_normalizer-3.4.7-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fe249cb4651fd12605b7288b24751d8bfd46d35f12a20b1ba33dea122e690df", size = 214061, upload-time = "2026-04-02T09:26:08.347Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/92/42bd3cefcf7687253fb86694b45f37b733c97f59af3724f356fa92b8c344/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:65bcd23054beab4d166035cabbc868a09c1a49d1efe458fe8e4361215df40265", size = 199239, upload-time = "2026-04-02T09:26:09.823Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/3d/069e7184e2aa3b3cddc700e3dd267413dc259854adc3380421c805c6a17d/charset_normalizer-3.4.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:08e721811161356f97b4059a9ba7bafb23ea5ee2255402c42881c214e173c6b4", size = 210173, upload-time = "2026-04-02T09:26:10.953Z" },
+ { url = "https://files.pythonhosted.org/packages/62/51/9d56feb5f2e7074c46f93e0ebdbe61f0848ee246e2f0d89f8e20b89ebb8f/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e060d01aec0a910bdccb8be71faf34e7799ce36950f8294c8bf612cba65a2c9e", size = 209841, upload-time = "2026-04-02T09:26:12.142Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/59/893d8f99cc4c837dda1fe2f1139079703deb9f321aabcb032355de13b6c7/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:38c0109396c4cfc574d502df99742a45c72c08eff0a36158b6f04000043dbf38", size = 200304, upload-time = "2026-04-02T09:26:13.711Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/1d/ee6f3be3464247578d1ed5c46de545ccc3d3ff933695395c402c21fa6b77/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1c2a768fdd44ee4a9339a9b0b130049139b8ce3c01d2ce09f67f5a68048d477c", size = 229455, upload-time = "2026-04-02T09:26:14.941Z" },
+ { url = "https://files.pythonhosted.org/packages/54/bb/8fb0a946296ea96a488928bdce8ef99023998c48e4713af533e9bb98ef07/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:1a87ca9d5df6fe460483d9a5bbf2b18f620cbed41b432e2bddb686228282d10b", size = 210036, upload-time = "2026-04-02T09:26:16.478Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/bc/015b2387f913749f82afd4fcba07846d05b6d784dd16123cb66860e0237d/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d635aab80466bc95771bb78d5370e74d36d1fe31467b6b29b8b57b2a3cd7d22c", size = 224739, upload-time = "2026-04-02T09:26:17.751Z" },
+ { url = "https://files.pythonhosted.org/packages/17/ab/63133691f56baae417493cba6b7c641571a2130eb7bceba6773367ab9ec5/charset_normalizer-3.4.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ae196f021b5e7c78e918242d217db021ed2a6ace2bc6ae94c0fc596221c7f58d", size = 216277, upload-time = "2026-04-02T09:26:18.981Z" },
+ { url = "https://files.pythonhosted.org/packages/06/6d/3be70e827977f20db77c12a97e6a9f973631a45b8d186c084527e53e77a4/charset_normalizer-3.4.7-cp311-cp311-win32.whl", hash = "sha256:adb2597b428735679446b46c8badf467b4ca5f5056aae4d51a19f9570301b1ad", size = 147819, upload-time = "2026-04-02T09:26:20.295Z" },
+ { url = "https://files.pythonhosted.org/packages/20/d9/5f67790f06b735d7c7637171bbfd89882ad67201891b7275e51116ed8207/charset_normalizer-3.4.7-cp311-cp311-win_amd64.whl", hash = "sha256:8e385e4267ab76874ae30db04c627faaaf0b509e1ccc11a95b3fc3e83f855c00", size = 159281, upload-time = "2026-04-02T09:26:21.74Z" },
+ { url = "https://files.pythonhosted.org/packages/ca/83/6413f36c5a34afead88ce6f66684d943d91f233d76dd083798f9602b75ae/charset_normalizer-3.4.7-cp311-cp311-win_arm64.whl", hash = "sha256:d4a48e5b3c2a489fae013b7589308a40146ee081f6f509e047e0e096084ceca1", size = 147843, upload-time = "2026-04-02T09:26:22.901Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" },
+ { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" },
+ { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" },
+ { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" },
+ { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" },
+ { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" },
+ { url = "https://files.pythonhosted.org/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" },
+ { url = "https://files.pythonhosted.org/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" },
+ { url = "https://files.pythonhosted.org/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" },
+ { url = "https://files.pythonhosted.org/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" },
+ { url = "https://files.pythonhosted.org/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" },
+ { url = "https://files.pythonhosted.org/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" },
+ { url = "https://files.pythonhosted.org/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" },
+ { url = "https://files.pythonhosted.org/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" },
+ { url = "https://files.pythonhosted.org/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" },
+ { url = "https://files.pythonhosted.org/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" },
+ { url = "https://files.pythonhosted.org/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" },
+ { url = "https://files.pythonhosted.org/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" },
+ { url = "https://files.pythonhosted.org/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" },
+ { url = "https://files.pythonhosted.org/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" },
+ { url = "https://files.pythonhosted.org/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" },
+ { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" },
+]
+
+[[package]]
+name = "click"
+version = "8.3.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
+]
+
+[[package]]
+name = "cloudpickle"
+version = "3.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" },
+]
+
+[[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 = "comm"
+version = "0.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" },
+]
+
+[[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/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" },
+ { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" },
+ { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" },
+ { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" },
+ { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" },
+ { 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" },
+ { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" },
+ { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" },
+]
+
+[[package]]
+name = "coverage"
+version = "7.13.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4b/37/d24c8f8220ff07b839b2c043ea4903a33b0f455abe673ae3c03bbdb7f212/coverage-7.13.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66a80c616f80181f4d643b0f9e709d97bcea413ecd9631e1dedc7401c8e6695d", size = 219381, upload-time = "2026-03-17T10:30:14.68Z" },
+ { url = "https://files.pythonhosted.org/packages/35/8b/cd129b0ca4afe886a6ce9d183c44d8301acbd4ef248622e7c49a23145605/coverage-7.13.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:145ede53ccbafb297c1c9287f788d1bc3efd6c900da23bf6931b09eafc931587", size = 219880, upload-time = "2026-03-17T10:30:16.231Z" },
+ { url = "https://files.pythonhosted.org/packages/55/2f/e0e5b237bffdb5d6c530ce87cc1d413a5b7d7dfd60fb067ad6d254c35c76/coverage-7.13.5-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0672854dc733c342fa3e957e0605256d2bf5934feeac328da9e0b5449634a642", size = 250303, upload-time = "2026-03-17T10:30:17.748Z" },
+ { url = "https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b", size = 252218, upload-time = "2026-03-17T10:30:19.804Z" },
+ { url = "https://files.pythonhosted.org/packages/da/69/2f47bb6fa1b8d1e3e5d0c4be8ccb4313c63d742476a619418f85740d597b/coverage-7.13.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be3d4bbad9d4b037791794ddeedd7d64a56f5933a2c1373e18e9e568b9141686", size = 254326, upload-time = "2026-03-17T10:30:21.321Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/d0/79db81da58965bd29dabc8f4ad2a2af70611a57cba9d1ec006f072f30a54/coverage-7.13.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4d2afbc5cc54d286bfb54541aa50b64cdb07a718227168c87b9e2fb8f25e1743", size = 256267, upload-time = "2026-03-17T10:30:23.094Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/32/d0d7cc8168f91ddab44c0ce4806b969df5f5fdfdbb568eaca2dbc2a04936/coverage-7.13.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3ad050321264c49c2fa67bb599100456fc51d004b82534f379d16445da40fb75", size = 250430, upload-time = "2026-03-17T10:30:25.311Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/06/a055311d891ddbe231cd69fdd20ea4be6e3603ffebddf8704b8ca8e10a3c/coverage-7.13.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7300c8a6d13335b29bb76d7651c66af6bd8658517c43499f110ddc6717bfc209", size = 252017, upload-time = "2026-03-17T10:30:27.284Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/f6/d0fd2d21e29a657b5f77a2fe7082e1568158340dceb941954f776dce1b7b/coverage-7.13.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb07647a5738b89baab047f14edd18ded523de60f3b30e75c2acc826f79c839a", size = 250080, upload-time = "2026-03-17T10:30:29.481Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/ab/0d7fb2efc2e9a5eb7ddcc6e722f834a69b454b7e6e5888c3a8567ecffb31/coverage-7.13.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9adb6688e3b53adffefd4a52d72cbd8b02602bfb8f74dcd862337182fd4d1a4e", size = 253843, upload-time = "2026-03-17T10:30:31.301Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/6f/7467b917bbf5408610178f62a49c0ed4377bb16c1657f689cc61470da8ce/coverage-7.13.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c8d4bc913dd70b93488d6c496c77f3aff5ea99a07e36a18f865bca55adef8bd", size = 249802, upload-time = "2026-03-17T10:30:33.358Z" },
+ { url = "https://files.pythonhosted.org/packages/75/2c/1172fb689df92135f5bfbbd69fc83017a76d24ea2e2f3a1154007e2fb9f8/coverage-7.13.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0e3c426ffc4cd952f54ee9ffbdd10345709ecc78a3ecfd796a57236bfad0b9b8", size = 250707, upload-time = "2026-03-17T10:30:35.2Z" },
+ { url = "https://files.pythonhosted.org/packages/67/21/9ac389377380a07884e3b48ba7a620fcd9dbfaf1d40565facdc6b36ec9ef/coverage-7.13.5-cp311-cp311-win32.whl", hash = "sha256:259b69bb83ad9894c4b25be2528139eecba9a82646ebdda2d9db1ba28424a6bf", size = 221880, upload-time = "2026-03-17T10:30:36.775Z" },
+ { url = "https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl", hash = "sha256:258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9", size = 222816, upload-time = "2026-03-17T10:30:38.891Z" },
+ { url = "https://files.pythonhosted.org/packages/12/a6/1d3f6155fb0010ca68eba7fe48ca6c9da7385058b77a95848710ecf189b1/coverage-7.13.5-cp311-cp311-win_arm64.whl", hash = "sha256:bff95879c33ec8da99fc9b6fe345ddb5be6414b41d6d1ad1c8f188d26f36e028", size = 221483, upload-time = "2026-03-17T10:30:40.463Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" },
+ { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" },
+ { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" },
+ { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" },
+ { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" },
+ { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" },
+ { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" },
+ { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" },
+ { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" },
+ { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" },
+ { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" },
+ { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" },
+ { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" },
+ { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" },
+ { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" },
+ { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" },
+ { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/77/39703f0d1d4b478bfd30191d3c14f53caf596fac00efb3f8f6ee23646439/coverage-7.13.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fbabfaceaeb587e16f7008f7795cd80d20ec548dc7f94fbb0d4ec2e038ce563f", size = 219621, upload-time = "2026-03-17T10:32:08.589Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/3e/51dff36d99ae14639a133d9b164d63e628532e2974d8b1edb99dd1ebc733/coverage-7.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9bb2a28101a443669a423b665939381084412b81c3f8c0fcfbac57f4e30b5b8e", size = 219953, upload-time = "2026-03-17T10:32:10.507Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/6c/1f1917b01eb647c2f2adc9962bd66c79eb978951cab61bdc1acab3290c07/coverage-7.13.5-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bd3a2fbc1c6cccb3c5106140d87cc6a8715110373ef42b63cf5aea29df8c217a", size = 250992, upload-time = "2026-03-17T10:32:12.41Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e5/06b1f88f42a5a99df42ce61208bdec3bddb3d261412874280a19796fc09c/coverage-7.13.5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6c36ddb64ed9d7e496028d1d00dfec3e428e0aabf4006583bb1839958d280510", size = 253503, upload-time = "2026-03-17T10:32:14.449Z" },
+ { url = "https://files.pythonhosted.org/packages/80/28/2a148a51e5907e504fa7b85490277734e6771d8844ebcc48764a15e28155/coverage-7.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:380e8e9084d8eb38db3a9176a1a4f3c0082c3806fa0dc882d1d87abc3c789247", size = 254852, upload-time = "2026-03-17T10:32:16.56Z" },
+ { url = "https://files.pythonhosted.org/packages/61/77/50e8d3d85cc0b7ebe09f30f151d670e302c7ff4a1bf6243f71dd8b0981fa/coverage-7.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e808af52a0513762df4d945ea164a24b37f2f518cbe97e03deaa0ee66139b4d6", size = 257161, upload-time = "2026-03-17T10:32:19.004Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/c4/b5fd1d4b7bf8d0e75d997afd3925c59ba629fc8616f1b3aae7605132e256/coverage-7.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e301d30dd7e95ae068671d746ba8c34e945a82682e62918e41b2679acd2051a0", size = 251021, upload-time = "2026-03-17T10:32:21.344Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/66/6ea21f910e92d69ef0b1c3346ea5922a51bad4446c9126db2ae96ee24c4c/coverage-7.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:800bc829053c80d240a687ceeb927a94fd108bbdc68dfbe505d0d75ab578a882", size = 252858, upload-time = "2026-03-17T10:32:23.506Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/ea/879c83cb5d61aa2a35fb80e72715e92672daef8191b84911a643f533840c/coverage-7.13.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:0b67af5492adb31940ee418a5a655c28e48165da5afab8c7fa6fd72a142f8740", size = 250823, upload-time = "2026-03-17T10:32:25.516Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/fb/616d95d3adb88b9803b275580bdeee8bd1b69a886d057652521f83d7322f/coverage-7.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c9136ff29c3a91e25b1d1552b5308e53a1e0653a23e53b6366d7c2dcbbaf8a16", size = 255099, upload-time = "2026-03-17T10:32:27.944Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/93/25e6917c90ec1c9a56b0b26f6cad6408e5f13bb6b35d484a0d75c9cf000d/coverage-7.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:cff784eef7f0b8f6cb28804fbddcfa99f89efe4cc35fb5627e3ac58f91ed3ac0", size = 250638, upload-time = "2026-03-17T10:32:29.914Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/7b/dc1776b0464145a929deed214aef9fb1493f159b59ff3c7eeeedf91eddd0/coverage-7.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:68a4953be99b17ac3c23b6efbc8a38330d99680c9458927491d18700ef23ded0", size = 252295, upload-time = "2026-03-17T10:32:31.981Z" },
+ { url = "https://files.pythonhosted.org/packages/ea/fb/99cbbc56a26e07762a2740713f3c8f9f3f3106e3a3dd8cc4474954bccd34/coverage-7.13.5-cp314-cp314-win32.whl", hash = "sha256:35a31f2b1578185fbe6aa2e74cea1b1d0bbf4c552774247d9160d29b80ed56cc", size = 222360, upload-time = "2026-03-17T10:32:34.233Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633", size = 223174, upload-time = "2026-03-17T10:32:36.369Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/f2/24d84e1dfe70f8ac9fdf30d338239860d0d1d5da0bda528959d0ebc9da28/coverage-7.13.5-cp314-cp314-win_arm64.whl", hash = "sha256:1b11eef33edeae9d142f9b4358edb76273b3bfd30bc3df9a4f95d0e49caf94e8", size = 221739, upload-time = "2026-03-17T10:32:38.736Z" },
+ { url = "https://files.pythonhosted.org/packages/60/5b/4a168591057b3668c2428bff25dd3ebc21b629d666d90bcdfa0217940e84/coverage-7.13.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10a0c37f0b646eaff7cce1874c31d1f1ccb297688d4c747291f4f4c70741cc8b", size = 220351, upload-time = "2026-03-17T10:32:41.196Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/21/1fd5c4dbfe4a58b6b99649125635df46decdfd4a784c3cd6d410d303e370/coverage-7.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b5db73ba3c41c7008037fa731ad5459fc3944cb7452fc0aa9f822ad3533c583c", size = 220612, upload-time = "2026-03-17T10:32:43.204Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/fe/2a924b3055a5e7e4512655a9d4609781b0d62334fa0140c3e742926834e2/coverage-7.13.5-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:750db93a81e3e5a9831b534be7b1229df848b2e125a604fe6651e48aa070e5f9", size = 261985, upload-time = "2026-03-17T10:32:45.514Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/0d/c8928f2bd518c45990fe1a2ab8db42e914ef9b726c975facc4282578c3eb/coverage-7.13.5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9ddb4f4a5479f2539644be484da179b653273bca1a323947d48ab107b3ed1f29", size = 264107, upload-time = "2026-03-17T10:32:47.971Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/ae/4ae35bbd9a0af9d820362751f0766582833c211224b38665c0f8de3d487f/coverage-7.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8a7a2049c14f413163e2bdabd37e41179b1d1ccb10ffc6ccc4b7a718429c607", size = 266513, upload-time = "2026-03-17T10:32:50.1Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/20/d326174c55af36f74eac6ae781612d9492f060ce8244b570bb9d50d9d609/coverage-7.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1c85e0b6c05c592ea6d8768a66a254bfb3874b53774b12d4c89c481eb78cb90", size = 267650, upload-time = "2026-03-17T10:32:52.391Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/5e/31484d62cbd0eabd3412e30d74386ece4a0837d4f6c3040a653878bfc019/coverage-7.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:777c4d1eff1b67876139d24288aaf1817f6c03d6bae9c5cc8d27b83bcfe38fe3", size = 261089, upload-time = "2026-03-17T10:32:54.544Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/d8/49a72d6de146eebb0b7e48cc0f4bc2c0dd858e3d4790ab2b39a2872b62bd/coverage-7.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6697e29b93707167687543480a40f0db8f356e86d9f67ddf2e37e2dfd91a9dab", size = 263982, upload-time = "2026-03-17T10:32:56.803Z" },
+ { url = "https://files.pythonhosted.org/packages/06/3b/0351f1bd566e6e4dd39e978efe7958bde1d32f879e85589de147654f57bb/coverage-7.13.5-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8fdf453a942c3e4d99bd80088141c4c6960bb232c409d9c3558e2dbaa3998562", size = 261579, upload-time = "2026-03-17T10:32:59.466Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/ce/796a2a2f4017f554d7810f5c573449b35b1e46788424a548d4d19201b222/coverage-7.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:32ca0c0114c9834a43f045a87dcebd69d108d8ffb666957ea65aa132f50332e2", size = 265316, upload-time = "2026-03-17T10:33:01.847Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/16/d5ae91455541d1a78bc90abf495be600588aff8f6db5c8b0dae739fa39c9/coverage-7.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8769751c10f339021e2638cd354e13adeac54004d1941119b2c96fe5276d45ea", size = 260427, upload-time = "2026-03-17T10:33:03.945Z" },
+ { url = "https://files.pythonhosted.org/packages/48/11/07f413dba62db21fb3fad5d0de013a50e073cc4e2dc4306e770360f6dfc8/coverage-7.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cec2d83125531bd153175354055cdb7a09987af08a9430bd173c937c6d0fba2a", size = 262745, upload-time = "2026-03-17T10:33:06.285Z" },
+ { url = "https://files.pythonhosted.org/packages/91/15/d792371332eb4663115becf4bad47e047d16234b1aff687b1b18c58d60ae/coverage-7.13.5-cp314-cp314t-win32.whl", hash = "sha256:0cd9ed7a8b181775459296e402ca4fb27db1279740a24e93b3b41942ebe4b215", size = 223146, upload-time = "2026-03-17T10:33:08.756Z" },
+ { url = "https://files.pythonhosted.org/packages/db/51/37221f59a111dca5e85be7dbf09696323b5b9f13ff65e0641d535ed06ea8/coverage-7.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:301e3b7dfefecaca37c9f1aa6f0049b7d4ab8dd933742b607765d757aca77d43", size = 224254, upload-time = "2026-03-17T10:33:11.174Z" },
+ { url = "https://files.pythonhosted.org/packages/54/83/6acacc889de8987441aa7d5adfbdbf33d288dad28704a67e574f1df9bcbb/coverage-7.13.5-cp314-cp314t-win_arm64.whl", hash = "sha256:9dacc2ad679b292709e0f5fc1ac74a6d4d5562e424058962c7bb0c658ad25e45", size = 222276, upload-time = "2026-03-17T10:33:13.466Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" },
+]
+
+[package.optional-dependencies]
+toml = [
+ { name = "tomli", marker = "python_full_version <= '3.11'" },
+]
+
+[[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 = "dask"
+version = "2026.1.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "click" },
+ { name = "cloudpickle" },
+ { name = "fsspec" },
+ { name = "importlib-metadata", marker = "python_full_version < '3.12'" },
+ { name = "packaging" },
+ { name = "partd" },
+ { name = "pyyaml" },
+ { name = "toolz" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/bd/52/b0f9172b22778def907db1ff173249e4eb41f054b46a9c83b1528aaf811f/dask-2026.1.2.tar.gz", hash = "sha256:1136683de2750d98ea792670f7434e6c1cfce90cab2cc2f2495a9e60fd25a4fc", size = 10997838, upload-time = "2026-01-30T21:04:20.54Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/23/d39ccc4ed76222db31530b0a7d38876fdb7673e23f838e8d8f0ed4651a4f/dask-2026.1.2-py3-none-any.whl", hash = "sha256:46a0cf3b8d87f78a3d2e6b145aea4418a6d6d606fe6a16c79bd8ca2bb862bc91", size = 1482084, upload-time = "2026-01-30T21:04:18.363Z" },
+]
+
+[[package]]
+name = "debugpy"
+version = "1.8.20"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" },
+ { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" },
+ { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" },
+ { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" },
+ { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" },
+ { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" },
+]
+
+[[package]]
+name = "decorator"
+version = "5.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "docutils"
+version = "0.22.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" },
+]
+
+[[package]]
+name = "executing"
+version = "2.2.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
+]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.21.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" },
+]
+
+[[package]]
+name = "fonttools"
+version = "4.62.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" },
+ { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" },
+ { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" },
+ { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" },
+ { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" },
+ { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" },
+ { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" },
+ { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" },
+ { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" },
+ { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" },
+ { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" },
+ { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" },
+ { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" },
+ { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" },
+ { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" },
+ { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" },
+ { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" },
+ { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" },
+ { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" },
+ { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" },
+ { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" },
+]
+
+[[package]]
+name = "fsspec"
+version = "2026.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" },
+]
+
+[[package]]
+name = "h5netcdf"
+version = "1.8.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ef/03/92d6cc02c0055158167255980461155d6e17f1c4143c03f8bcc18d3e3f3a/h5netcdf-1.8.1.tar.gz", hash = "sha256:9b396a4cc346050fc1a4df8523bc1853681ec3544e0449027ae397cb953c7a16", size = 78679, upload-time = "2026-01-23T07:35:31.233Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/8b/88f16936a8e8070a83d36239555227ecd91728f9ef222c5382cda07e0fd6/h5netcdf-1.8.1-py3-none-any.whl", hash = "sha256:a76ed7cfc9b8a8908ea7057c4e57e27307acff1049b7f5ed52db6c2247636879", size = 62915, upload-time = "2026-01-23T07:35:30.195Z" },
+]
+
+[[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/ba/95/a825894f3e45cbac7554c4e97314ce886b233a20033787eda755ca8fecc7/h5py-3.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:719439d14b83f74eeb080e9650a6c7aa6d0d9ea0ca7f804347b05fac6fbf18af", size = 3721663, upload-time = "2026-03-06T13:47:49.599Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/3b/38ff88b347c3e346cda1d3fc1b65a7aa75d40632228d8b8a5d7b58508c24/h5py-3.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3f0a0e136f2e95dd0b67146abb6668af4f1a69c81ef8651a2d316e8e01de447", size = 3087630, upload-time = "2026-03-06T13:47:51.249Z" },
+ { url = "https://files.pythonhosted.org/packages/98/a8/2594cef906aee761601eff842c7dc598bea2b394a3e1c00966832b8eeb7c/h5py-3.16.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a6fbc5367d4046801f9b7db9191b31895f22f1c6df1f9987d667854cac493538", size = 4823472, upload-time = "2026-03-06T13:47:53.085Z" },
+ { url = "https://files.pythonhosted.org/packages/52/a0/c1f604538ff6db22a0690be2dc44ab59178e115f63c917794e529356ab23/h5py-3.16.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb1720028d99040792bb2fb31facb8da44a6f29df7697e0b84f0d79aff2e9bd3", size = 5027150, upload-time = "2026-03-06T13:47:55.043Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/fd/301739083c2fc4fd89950f9bcfce75d6e14b40b0ca3d40e48a8993d1722c/h5py-3.16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:314b6054fe0b1051c2b0cb2df5cbdab15622fb05e80f202e3b6a5eee0d6fe365", size = 4814544, upload-time = "2026-03-06T13:47:56.893Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/42/2193ed41ccee78baba8fcc0cff2c925b8b9ee3793305b23e1f22c20bf4c7/h5py-3.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ffbab2fedd6581f6aa31cf1639ca2cb86e02779de525667892ebf4cc9fd26434", size = 5034013, upload-time = "2026-03-06T13:47:59.01Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/20/e6c0ff62ca2ad1a396a34f4380bafccaaf8791ff8fccf3d995a1fc12d417/h5py-3.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:17d1f1630f92ad74494a9a7392ab25982ce2b469fc62da6074c0ce48366a2999", size = 3191673, upload-time = "2026-03-06T13:48:00.626Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/48/239cbe352ac4f2b8243a8e620fa1a2034635f633731493a7ff1ed71e8658/h5py-3.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b9c49dd58dc44cf70af944784e2c2038b6f799665d0dcbbc812a26e0faa859", size = 2673834, upload-time = "2026-03-06T13:48:02.579Z" },
+ { 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 = "idna"
+version = "3.13"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" },
+]
+
+[[package]]
+name = "imagesize"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6c/e6/7bf14eeb8f8b7251141944835abd42eb20a658d89084b7e1f3e5fe394090/imagesize-2.0.0.tar.gz", hash = "sha256:8e8358c4a05c304f1fccf7ff96f036e7243a189e9e42e90851993c558cfe9ee3", size = 1773045, upload-time = "2026-03-03T14:18:29.941Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5f/53/fb7122b71361a0d121b669dcf3d31244ef75badbbb724af388948de543e2/imagesize-2.0.0-py2.py3-none-any.whl", hash = "sha256:5667c5bbb57ab3f1fa4bc366f4fbc971db3d5ed011fd2715fd8001f782718d96", size = 9441, upload-time = "2026-03-03T14:18:27.892Z" },
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "zipp" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" },
+]
+
+[[package]]
+name = "influxdb-client"
+version = "1.50.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "python-dateutil" },
+ { name = "reactivex" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/07/23/77a945465a556a8067917ba84bac04e312cee2a4fe03d41198fe79ed9c84/influxdb_client-1.50.0.tar.gz", hash = "sha256:c2a4906573097103fa6f9a2ab08efe2eb48c2fed60b8129a3e320affde445743", size = 386749, upload-time = "2026-01-23T09:39:39.322Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7f/ec/6b120b4a86f6fadc7ddb1d7c7cdcb15bcfb332deb022ab60df51bcd4494c/influxdb_client-1.50.0-py3-none-any.whl", hash = "sha256:f172975cf7f0c95bfe74f288b31273393b164d2c58a948de55497d9956ab49be", size = 746289, upload-time = "2026-01-23T09:39:37.377Z" },
+]
+
+[[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 = "instrumentserver"
+version = "0.0.1"
+source = { editable = "." }
+dependencies = [
+ { name = "jsonschema" },
+ { name = "numpy" },
+ { name = "pandas" },
+ { name = "pyqt5" },
+ { name = "pyyaml" },
+ { name = "pyzmq" },
+ { name = "qcodes" },
+ { name = "qtpy" },
+ { name = "ruamel-yaml" },
+ { name = "scipy" },
+]
+
+[package.optional-dependencies]
+monitoring = [
+ { name = "influxdb-client" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "mypy" },
+ { name = "pytest" },
+ { name = "pytest-cov" },
+ { name = "pytest-qt" },
+ { name = "ruff" },
+]
+docs = [
+ { name = "linkify-it-py" },
+ { name = "myst-parser" },
+ { name = "nbsphinx" },
+ { name = "pydata-sphinx-theme" },
+ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "influxdb-client", marker = "extra == 'monitoring'" },
+ { name = "jsonschema" },
+ { name = "numpy" },
+ { name = "pandas" },
+ { name = "pyqt5" },
+ { name = "pyyaml" },
+ { name = "pyzmq" },
+ { name = "qcodes" },
+ { name = "qtpy" },
+ { name = "ruamel-yaml" },
+ { name = "scipy" },
+]
+provides-extras = ["monitoring"]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "mypy" },
+ { name = "pytest", specifier = ">=9.0.2" },
+ { name = "pytest-cov" },
+ { name = "pytest-qt", specifier = ">=4.5.0" },
+ { name = "ruff" },
+]
+docs = [
+ { name = "linkify-it-py" },
+ { name = "myst-parser" },
+ { name = "nbsphinx" },
+ { name = "pydata-sphinx-theme" },
+ { name = "sphinx" },
+]
+
+[[package]]
+name = "ipykernel"
+version = "7.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "appnope", marker = "sys_platform == 'darwin'" },
+ { name = "comm" },
+ { name = "debugpy" },
+ { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "jupyter-client" },
+ { name = "jupyter-core" },
+ { name = "matplotlib-inline" },
+ { name = "nest-asyncio" },
+ { name = "packaging" },
+ { name = "psutil" },
+ { name = "pyzmq" },
+ { name = "tornado" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ca/8d/b68b728e2d06b9e0051019640a40a9eb7a88fcd82c2e1b5ce70bef5ff044/ipykernel-7.2.0.tar.gz", hash = "sha256:18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e", size = 176046, upload-time = "2026-02-06T16:43:27.403Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl", hash = "sha256:3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661", size = 118788, upload-time = "2026-02-06T16:43:25.149Z" },
+]
+
+[[package]]
+name = "ipython"
+version = "9.10.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.12' and sys_platform == 'win32'",
+ "python_full_version < '3.12' and sys_platform == 'emscripten'",
+ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" },
+ { name = "decorator", marker = "python_full_version < '3.12'" },
+ { name = "ipython-pygments-lexers", marker = "python_full_version < '3.12'" },
+ { name = "jedi", marker = "python_full_version < '3.12'" },
+ { name = "matplotlib-inline", marker = "python_full_version < '3.12'" },
+ { name = "pexpect", marker = "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
+ { name = "prompt-toolkit", marker = "python_full_version < '3.12'" },
+ { name = "pygments", marker = "python_full_version < '3.12'" },
+ { name = "stack-data", marker = "python_full_version < '3.12'" },
+ { name = "traitlets", marker = "python_full_version < '3.12'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a6/60/2111715ea11f39b1535bed6024b7dec7918b71e5e5d30855a5b503056b50/ipython-9.10.0.tar.gz", hash = "sha256:cd9e656be97618a0676d058134cd44e6dc7012c0e5cb36a9ce96a8c904adaf77", size = 4426526, upload-time = "2026-02-02T10:00:33.594Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl", hash = "sha256:c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d", size = 622774, upload-time = "2026-02-02T10:00:31.503Z" },
+]
+
+[[package]]
+name = "ipython"
+version = "9.11.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.15' and sys_platform == 'win32'",
+ "python_full_version == '3.14.*' and sys_platform == 'win32'",
+ "python_full_version >= '3.15' and sys_platform == 'emscripten'",
+ "python_full_version == '3.14.*' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+dependencies = [
+ { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" },
+ { name = "decorator", marker = "python_full_version >= '3.12'" },
+ { name = "ipython-pygments-lexers", marker = "python_full_version >= '3.12'" },
+ { name = "jedi", marker = "python_full_version >= '3.12'" },
+ { name = "matplotlib-inline", marker = "python_full_version >= '3.12'" },
+ { name = "pexpect", marker = "python_full_version >= '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'" },
+ { name = "prompt-toolkit", marker = "python_full_version >= '3.12'" },
+ { name = "pygments", marker = "python_full_version >= '3.12'" },
+ { name = "stack-data", marker = "python_full_version >= '3.12'" },
+ { name = "traitlets", marker = "python_full_version >= '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/86/28/a4698eda5a8928a45d6b693578b135b753e14fa1c2b36ee9441e69a45576/ipython-9.11.0.tar.gz", hash = "sha256:2a94bc4406b22ecc7e4cb95b98450f3ea493a76bec8896cda11b78d7752a6667", size = 4427354, upload-time = "2026-03-05T08:57:30.549Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl", hash = "sha256:6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19", size = 624222, upload-time = "2026-03-05T08:57:28.94Z" },
+]
+
+[[package]]
+name = "ipython-pygments-lexers"
+version = "1.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" },
+]
+
+[[package]]
+name = "ipywidgets"
+version = "8.1.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "comm" },
+ { name = "ipython", version = "9.10.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "ipython", version = "9.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "jupyterlab-widgets" },
+ { name = "traitlets" },
+ { name = "widgetsnbextension" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" },
+]
+
+[[package]]
+name = "jedi"
+version = "0.19.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "parso" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" },
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markupsafe" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.26.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "jupyter-client"
+version = "8.8.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-core" },
+ { name = "python-dateutil" },
+ { name = "pyzmq" },
+ { name = "tornado" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" },
+]
+
+[[package]]
+name = "jupyter-core"
+version = "5.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "platformdirs" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" },
+]
+
+[[package]]
+name = "jupyterlab-pygments"
+version = "0.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" },
+]
+
+[[package]]
+name = "jupyterlab-widgets"
+version = "3.0.16"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" },
+]
+
+[[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/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" },
+ { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" },
+ { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" },
+ { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" },
+ { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" },
+ { 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" },
+ { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" },
+ { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" },
+ { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" },
+]
+
+[[package]]
+name = "librt"
+version = "0.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/6b/3d5c13fb3e3c4f43206c8f9dfed13778c2ed4f000bacaa0b7ce3c402a265/librt-0.9.0.tar.gz", hash = "sha256:a0951822531e7aee6e0dfb556b30d5ee36bbe234faf60c20a16c01be3530869d", size = 184368, upload-time = "2026-04-09T16:06:26.173Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e2/1e/2ec7afcebcf3efea593d13aee18bbcfdd3a243043d848ebf385055e9f636/librt-0.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90904fac73c478f4b83f4ed96c99c8208b75e6f9a8a1910548f69a00f1eaa671", size = 67155, upload-time = "2026-04-09T16:04:42.933Z" },
+ { url = "https://files.pythonhosted.org/packages/18/77/72b85afd4435268338ad4ec6231b3da8c77363f212a0227c1ff3b45e4d35/librt-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:789fff71757facc0738e8d89e3b84e4f0251c1c975e85e81b152cdaca927cc2d", size = 69916, upload-time = "2026-04-09T16:04:44.042Z" },
+ { url = "https://files.pythonhosted.org/packages/27/fb/948ea0204fbe2e78add6d46b48330e58d39897e425560674aee302dca81c/librt-0.9.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1bf465d1e5b0a27713862441f6467b5ab76385f4ecf8f1f3a44f8aa3c695b4b6", size = 199635, upload-time = "2026-04-09T16:04:45.5Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/cd/894a29e251b296a27957856804cfd21e93c194aa131de8bb8032021be07e/librt-0.9.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f819e0c6413e259a17a7c0d49f97f405abadd3c2a316a3b46c6440b7dbbedbb1", size = 211051, upload-time = "2026-04-09T16:04:47.016Z" },
+ { url = "https://files.pythonhosted.org/packages/18/8f/dcaed0bc084a35f3721ff2d081158db569d2c57ea07d35623ddaca5cfc8e/librt-0.9.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e0785c2fb4a81e1aece366aa3e2e039f4a4d7d21aaaded5227d7f3c703427882", size = 224031, upload-time = "2026-04-09T16:04:48.207Z" },
+ { url = "https://files.pythonhosted.org/packages/03/44/88f6c1ed1132cd418601cc041fbd92fed28b3a09f39de81978e0822d13ff/librt-0.9.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:80b25c7b570a86c03b5da69e665809deb39265476e8e21d96a9328f9762f9990", size = 218069, upload-time = "2026-04-09T16:04:50.025Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/90/7d02e981c2db12188d82b4410ff3e35bfdb844b26aecd02233626f46af2b/librt-0.9.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d4d16b608a1c43d7e33142099a75cd93af482dadce0bf82421e91cad077157f4", size = 224857, upload-time = "2026-04-09T16:04:51.684Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/c3/c77e706b7215ca32e928d47535cf13dbc3d25f096f84ddf8fbc06693e229/librt-0.9.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:194fc1a32e1e21fe809d38b5faea66cc65eaa00217c8901fbdb99866938adbdb", size = 219865, upload-time = "2026-04-09T16:04:52.949Z" },
+ { url = "https://files.pythonhosted.org/packages/52/d1/32b0c1a0eb8461c70c11656c46a29f760b7c7edf3c36d6f102470c17170f/librt-0.9.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:8c6bc1384d9738781cfd41d09ad7f6e8af13cfea2c75ece6bd6d2566cdea2076", size = 218451, upload-time = "2026-04-09T16:04:54.174Z" },
+ { url = "https://files.pythonhosted.org/packages/74/d1/adfd0f9c44761b1d49b1bec66173389834c33ee2bd3c7fd2e2367f1942d4/librt-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15cb151e52a044f06e54ac7f7b47adbfc89b5c8e2b63e1175a9d587c43e8942a", size = 241300, upload-time = "2026-04-09T16:04:55.452Z" },
+ { url = "https://files.pythonhosted.org/packages/09/b0/9074b64407712f0003c27f5b1d7655d1438979155f049720e8a1abd9b1a1/librt-0.9.0-cp311-cp311-win32.whl", hash = "sha256:f100bfe2acf8a3689af9d0cc660d89f17286c9c795f9f18f7b62dd1a6b247ae6", size = 55668, upload-time = "2026-04-09T16:04:56.689Z" },
+ { url = "https://files.pythonhosted.org/packages/24/19/40b77b77ce80b9389fb03971431b09b6b913911c38d412059e0b3e2a9ef2/librt-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:0b73e4266307e51c95e09c0750b7ec383c561d2e97d58e473f6f6a209952fbb8", size = 62976, upload-time = "2026-04-09T16:04:57.733Z" },
+ { url = "https://files.pythonhosted.org/packages/70/9d/9fa7a64041e29035cb8c575af5f0e3840be1b97b4c4d9061e0713f171849/librt-0.9.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc5518873822d2faa8ebdd2c1a4d7c8ef47b01a058495ab7924cb65bdbf5fc9a", size = 53502, upload-time = "2026-04-09T16:04:58.806Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/90/89ddba8e1c20b0922783cd93ed8e64f34dc05ab59c38a9c7e313632e20ff/librt-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9b3e3bc363f71bda1639a4ee593cb78f7fbfeacc73411ec0d4c92f00730010a4", size = 68332, upload-time = "2026-04-09T16:05:00.09Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/40/7aa4da1fb08bdeeb540cb07bfc8207cb32c5c41642f2594dbd0098a0662d/librt-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a09c2f5869649101738653a9b7ab70cf045a1105ac66cbb8f4055e61df78f2d", size = 70581, upload-time = "2026-04-09T16:05:01.213Z" },
+ { url = "https://files.pythonhosted.org/packages/48/ac/73a2187e1031041e93b7e3a25aae37aa6f13b838c550f7e0f06f66766212/librt-0.9.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ca8e133d799c948db2ab1afc081c333a825b5540475164726dcbf73537e5c2f", size = 203984, upload-time = "2026-04-09T16:05:02.542Z" },
+ { url = "https://files.pythonhosted.org/packages/5e/3d/23460d571e9cbddb405b017681df04c142fb1b04cbfce77c54b08e28b108/librt-0.9.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:603138ee838ee1583f1b960b62d5d0007845c5c423feb68e44648b1359014e27", size = 215762, upload-time = "2026-04-09T16:05:04.127Z" },
+ { url = "https://files.pythonhosted.org/packages/de/1e/42dc7f8ab63e65b20640d058e63e97fd3e482c1edbda3570d813b4d0b927/librt-0.9.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4003f70c56a5addd6aa0897f200dd59afd3bf7bcd5b3cce46dd21f925743bc2", size = 230288, upload-time = "2026-04-09T16:05:05.883Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/08/ca812b6d8259ad9ece703397f8ad5c03af5b5fedfce64279693d3ce4087c/librt-0.9.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:78042f6facfd98ecb25e9829c7e37cce23363d9d7c83bc5f72702c5059eb082b", size = 224103, upload-time = "2026-04-09T16:05:07.148Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/3f/620490fb2fa66ffd44e7f900254bc110ebec8dac6c1b7514d64662570e6f/librt-0.9.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a361c9434a64d70a7dbb771d1de302c0cc9f13c0bffe1cf7e642152814b35265", size = 232122, upload-time = "2026-04-09T16:05:08.386Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/83/12864700a1b6a8be458cf5d05db209b0d8e94ae281e7ec261dbe616597b4/librt-0.9.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:dd2c7e082b0b92e1baa4da28163a808672485617bc855cc22a2fd06978fa9084", size = 225045, upload-time = "2026-04-09T16:05:09.707Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/1b/845d339c29dc7dbc87a2e992a1ba8d28d25d0e0372f9a0a2ecebde298186/librt-0.9.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7e6274fd33fc5b2a14d41c9119629d3ff395849d8bcbc80cf637d9e8d2034da8", size = 227372, upload-time = "2026-04-09T16:05:10.942Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/fe/277985610269d926a64c606f761d58d3db67b956dbbf40024921e95e7fcb/librt-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5093043afb226ecfa1400120d1ebd4442b4f99977783e4f4f7248879009b227f", size = 248224, upload-time = "2026-04-09T16:05:12.254Z" },
+ { url = "https://files.pythonhosted.org/packages/92/1b/ee486d244b8de6b8b5dbaefabe6bfdd4a72e08f6353edf7d16d27114da8d/librt-0.9.0-cp312-cp312-win32.whl", hash = "sha256:9edcc35d1cae9fd5320171b1a838c7da8a5c968af31e82ecc3dff30b4be0957f", size = 55986, upload-time = "2026-04-09T16:05:13.529Z" },
+ { url = "https://files.pythonhosted.org/packages/89/7a/ba1737012308c17dc6d5516143b5dce9a2c7ba3474afd54e11f44a4d1ef3/librt-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc2917258e131ae5f958a4d872e07555b51cb7466a43433218061c74ef33745", size = 63260, upload-time = "2026-04-09T16:05:14.68Z" },
+ { url = "https://files.pythonhosted.org/packages/36/e4/01752c113da15127f18f7bf11142f5640038f062407a611c059d0036c6aa/librt-0.9.0-cp312-cp312-win_arm64.whl", hash = "sha256:90e6d5420fc8a300518d4d2288154ff45005e920425c22cbbfe8330f3f754bd9", size = 53694, upload-time = "2026-04-09T16:05:16.095Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/d7/1b3e26fffde1452d82f5666164858a81c26ebe808e7ae8c9c88628981540/librt-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29b68cd9714531672db62cc54f6e8ff981900f824d13fa0e00749189e13778e", size = 68367, upload-time = "2026-04-09T16:05:17.243Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/5b/c61b043ad2e091fbe1f2d35d14795e545d0b56b03edaa390fa1dcee3d160/librt-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d5c8a5929ac325729f6119802070b561f4db793dffc45e9ac750992a4ed4d22", size = 70595, upload-time = "2026-04-09T16:05:18.471Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/22/2448471196d8a73370aa2f23445455dc42712c21404081fcd7a03b9e0749/librt-0.9.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:756775d25ec8345b837ab52effee3ad2f3b2dfd6bbee3e3f029c517bd5d8f05a", size = 204354, upload-time = "2026-04-09T16:05:19.593Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/5e/39fc4b153c78cfd2c8a2dcb32700f2d41d2312aa1050513183be4540930d/librt-0.9.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b8f5d00b49818f4e2b1667db994488b045835e0ac16fe2f924f3871bd2b8ac5", size = 216238, upload-time = "2026-04-09T16:05:20.868Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/42/bc2d02d0fa7badfa63aa8d6dcd8793a9f7ef5a94396801684a51ed8d8287/librt-0.9.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c81aef782380f0f13ead670aae01825eb653b44b046aa0e5ebbb79f76ed4aa11", size = 230589, upload-time = "2026-04-09T16:05:22.305Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/7b/e2d95cc513866373692aa5edf98080d5602dd07cabfb9e5d2f70df2f25f7/librt-0.9.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66b58fed90a545328e80d575467244de3741e088c1af928f0b489ebec3ef3858", size = 224610, upload-time = "2026-04-09T16:05:23.647Z" },
+ { url = "https://files.pythonhosted.org/packages/31/d5/6cec4607e998eaba57564d06a1295c21b0a0c8de76e4e74d699e627bd98c/librt-0.9.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e78fb7419e07d98c2af4b8567b72b3eaf8cb05caad642e9963465569c8b2d87e", size = 232558, upload-time = "2026-04-09T16:05:25.025Z" },
+ { url = "https://files.pythonhosted.org/packages/95/8c/27f1d8d3aaf079d3eb26439bf0b32f1482340c3552e324f7db9dca858671/librt-0.9.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c3786f0f4490a5cd87f1ed6cefae833ad6b1060d52044ce0434a2e85893afd0", size = 225521, upload-time = "2026-04-09T16:05:26.311Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/d8/1e0d43b1c329b416017619469b3c3801a25a6a4ef4a1c68332aeaa6f72ca/librt-0.9.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8494cfc61e03542f2d381e71804990b3931175a29b9278fdb4a5459948778dc2", size = 227789, upload-time = "2026-04-09T16:05:27.624Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/b4/d3d842e88610fcd4c8eec7067b0c23ef2d7d3bff31496eded6a83b0f99be/librt-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:07cf11f769831186eeac424376e6189f20ace4f7263e2134bdb9757340d84d4d", size = 248616, upload-time = "2026-04-09T16:05:29.181Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/28/527df8ad0d1eb6c8bdfa82fc190f1f7c4cca5a1b6d7b36aeabf95b52d74d/librt-0.9.0-cp313-cp313-win32.whl", hash = "sha256:850d6d03177e52700af605fd60db7f37dcb89782049a149674d1a9649c2138fd", size = 56039, upload-time = "2026-04-09T16:05:30.709Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/a7/413652ad0d92273ee5e30c000fc494b361171177c83e57c060ecd3c21538/librt-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:a5af136bfba820d592f86c67affcef9b3ff4d4360ac3255e341e964489b48519", size = 63264, upload-time = "2026-04-09T16:05:31.881Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/0a/92c244309b774e290ddb15e93363846ae7aa753d9586b8aad511c5e6145b/librt-0.9.0-cp313-cp313-win_arm64.whl", hash = "sha256:4c4d0440a3a8e31d962340c3e1cc3fc9ee7febd34c8d8f770d06adb947779ea5", size = 53728, upload-time = "2026-04-09T16:05:33.31Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/c1/184e539543f06ea2912f4b92a5ffaede4f9b392689e3f00acbf8134bee92/librt-0.9.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:3f05d145df35dca5056a8bc3838e940efebd893a54b3e19b2dda39ceaa299bcb", size = 67830, upload-time = "2026-04-09T16:05:34.517Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ad/23399bdcb7afca819acacdef31b37ee59de261bd66b503a7995c03c4b0dc/librt-0.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c587494461ebd42229d0f1739f3aa34237dd9980623ecf1be8d3bcba79f4499", size = 70280, upload-time = "2026-04-09T16:05:35.649Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/0b/4542dc5a2b8772dbf92cafb9194701230157e73c14b017b6961a23598b03/librt-0.9.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0a2040f801406b93657a70b72fa12311063a319fee72ce98e1524da7200171f", size = 201925, upload-time = "2026-04-09T16:05:36.739Z" },
+ { url = "https://files.pythonhosted.org/packages/31/d4/8ee7358b08fd0cfce051ef96695380f09b3c2c11b77c9bfbc367c921cce5/librt-0.9.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f38bc489037eca88d6ebefc9c4d41a4e07c8e8b4de5188a9e6d290273ad7ebb1", size = 212381, upload-time = "2026-04-09T16:05:38.043Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/94/a2025fe442abedf8b038038dab3dba942009ad42b38ea064a1a9e6094241/librt-0.9.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3fd278f5e6bf7c75ccd6d12344eb686cc020712683363b66f46ac79d37c799f", size = 227065, upload-time = "2026-04-09T16:05:39.394Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/e9/b9fcf6afa909f957cfbbf918802f9dada1bd5d3c1da43d722fd6a310dc3f/librt-0.9.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fcbdf2a9ca24e87bbebb47f1fe34e531ef06f104f98c9ccfc953a3f3344c567a", size = 221333, upload-time = "2026-04-09T16:05:40.999Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/7c/ba54cd6aa6a3c8cd12757a6870e0c79a64b1e6327f5248dcff98423f4d43/librt-0.9.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e306d956cfa027fe041585f02a1602c32bfa6bb8ebea4899d373383295a6c62f", size = 229051, upload-time = "2026-04-09T16:05:42.605Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/4b/8cfdbad314c8677a0148bf0b70591d6d18587f9884d930276098a235461b/librt-0.9.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:465814ab157986acb9dfa5ccd7df944be5eefc0d08d31ec6e8d88bc71251d845", size = 222492, upload-time = "2026-04-09T16:05:43.842Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/d1/2eda69563a1a88706808decdce035e4b32755dbfbb0d05e1a65db9547ed1/librt-0.9.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:703f4ae36d6240bfe24f542bac784c7e4194ec49c3ba5a994d02891649e2d85b", size = 223849, upload-time = "2026-04-09T16:05:45.054Z" },
+ { url = "https://files.pythonhosted.org/packages/04/44/b2ed37df6be5b3d42cfe36318e0598e80843d5c6308dd63d0bf4e0ce5028/librt-0.9.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3be322a15ee5e70b93b7a59cfd074614f22cc8c9ff18bd27f474e79137ea8d3b", size = 245001, upload-time = "2026-04-09T16:05:46.34Z" },
+ { url = "https://files.pythonhosted.org/packages/47/e7/617e412426df89169dd2a9ed0cc8752d5763336252c65dbf945199915119/librt-0.9.0-cp314-cp314-win32.whl", hash = "sha256:b8da9f8035bb417770b1e1610526d87ad4fc58a2804dc4d79c53f6d2cf5a6eb9", size = 51799, upload-time = "2026-04-09T16:05:47.738Z" },
+ { url = "https://files.pythonhosted.org/packages/24/ed/c22ca4db0ca3cbc285e4d9206108746beda561a9792289c3c31281d7e9df/librt-0.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:b8bd70d5d816566a580d193326912f4a76ec2d28a97dc4cd4cc831c0af8e330e", size = 59165, upload-time = "2026-04-09T16:05:49.198Z" },
+ { url = "https://files.pythonhosted.org/packages/24/56/875398fafa4cbc8f15b89366fc3287304ddd3314d861f182a4b87595ace0/librt-0.9.0-cp314-cp314-win_arm64.whl", hash = "sha256:fc5758e2b7a56532dc33e3c544d78cbaa9ecf0a0f2a2da2df882c1d6b99a317f", size = 49292, upload-time = "2026-04-09T16:05:50.362Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/61/bc448ecbf9b2d69c5cff88fe41496b19ab2a1cbda0065e47d4d0d51c0867/librt-0.9.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f24b90b0e0c8cc9491fb1693ae91fe17cb7963153a1946395acdbdd5818429a4", size = 70175, upload-time = "2026-04-09T16:05:51.564Z" },
+ { url = "https://files.pythonhosted.org/packages/60/f2/c47bb71069a73e2f04e70acbd196c1e5cc411578ac99039a224b98920fd4/librt-0.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3fe56e80badb66fdcde06bef81bbaa5bfcf6fbd7aefb86222d9e369c38c6b228", size = 72951, upload-time = "2026-04-09T16:05:52.699Z" },
+ { url = "https://files.pythonhosted.org/packages/29/19/0549df59060631732df758e8886d92088da5fdbedb35b80e4643664e8412/librt-0.9.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:527b5b820b47a09e09829051452bb0d1dd2122261254e2a6f674d12f1d793d54", size = 225864, upload-time = "2026-04-09T16:05:53.895Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/f8/3b144396d302ac08e50f89e64452c38db84bc7b23f6c60479c5d3abd303c/librt-0.9.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d429bdd4ac0ab17c8e4a8af0ed2a7440b16eba474909ab357131018fe8c7e71", size = 241155, upload-time = "2026-04-09T16:05:55.191Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/ce/ee67ec14581de4043e61d05786d2aed6c9b5338816b7859bcf07455c6a9f/librt-0.9.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7202bdcac47d3a708271c4304a474a8605a4a9a4a709e954bf2d3241140aa938", size = 252235, upload-time = "2026-04-09T16:05:56.549Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/fa/0ead15daa2b293a54101550b08d4bafe387b7d4a9fc6d2b985602bae69b6/librt-0.9.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0d620e74897f8c2613b3c4e2e9c1e422eb46d2ddd07df540784d44117836af3", size = 244963, upload-time = "2026-04-09T16:05:57.858Z" },
+ { url = "https://files.pythonhosted.org/packages/29/68/9fbf9a9aa704ba87689e40017e720aced8d9a4d2b46b82451d8142f91ec9/librt-0.9.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d69fc39e627908f4c03297d5a88d9284b73f4d90b424461e32e8c2485e21c283", size = 257364, upload-time = "2026-04-09T16:05:59.686Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8d/9d60869f1b6716c762e45f66ed945b1e5dd649f7377684c3b176ae424648/librt-0.9.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:c2640e23d2b7c98796f123ffd95cf2022c7777aa8a4a3b98b36c570d37e85eee", size = 247661, upload-time = "2026-04-09T16:06:00.938Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ff/a5c365093962310bfdb4f6af256f191085078ffb529b3f0cbebb5b33ebe2/librt-0.9.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:451daa98463b7695b0a30aa56bf637831ea559e7b8101ac2ef6382e8eb15e29c", size = 248238, upload-time = "2026-04-09T16:06:02.537Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/3c/2d34365177f412c9e19c0a29f969d70f5343f27634b76b765a54d8b27705/librt-0.9.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:928bd06eca2c2bbf4349e5b817f837509b0604342e65a502de1d50a7570afd15", size = 269457, upload-time = "2026-04-09T16:06:03.833Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/cd/de45b239ea3bdf626f982a00c14bfcf2e12d261c510ba7db62c5969a27cd/librt-0.9.0-cp314-cp314t-win32.whl", hash = "sha256:a9c63e04d003bc0fb6a03b348018b9a3002f98268200e22cc80f146beac5dc40", size = 52453, upload-time = "2026-04-09T16:06:05.229Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/f9/bfb32ae428aa75c0c533915622176f0a17d6da7b72b5a3c6363685914f70/librt-0.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f162af66a2ed3f7d1d161a82ca584efd15acd9c1cff190a373458c32f7d42118", size = 60044, upload-time = "2026-04-09T16:06:06.398Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/47/7d70414bcdbb3bc1f458a8d10558f00bbfdb24e5a11740fc8197e12c3255/librt-0.9.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a4b25c6c25cac5d0d9d6d6da855195b254e0021e513e0249f0e3b444dc6e0e61", size = 50009, upload-time = "2026-04-09T16:06:07.995Z" },
+]
+
+[[package]]
+name = "linkify-it-py"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "uc-micro-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/c9/06ea13676ef354f0af6169587ae292d3e2406e212876a413bf9eece4eb23/linkify_it_py-2.1.0.tar.gz", hash = "sha256:43360231720999c10e9328dc3691160e27a718e280673d444c38d7d3aaa3b98b", size = 29158, upload-time = "2026-03-01T07:48:47.683Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b4/de/88b3be5c31b22333b3ca2f6ff1de4e863d8fe45aaea7485f591970ec1d3e/linkify_it_py-2.1.0-py3-none-any.whl", hash = "sha256:0d252c1594ecba2ecedc444053db5d3a9b7ec1b0dd929c8f1d74dce89f86c05e", size = 19878, upload-time = "2026-03-01T07:48:46.098Z" },
+]
+
+[[package]]
+name = "locket"
+version = "1.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2f/83/97b29fe05cb6ae28d2dbd30b81e2e402a3eed5f460c26e9eaa5895ceacf5/locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632", size = 4350, upload-time = "2022-04-20T22:04:44.312Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/db/bc/83e112abc66cd466c6b83f99118035867cecd41802f8d044638aa78a106e/locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3", size = 4398, upload-time = "2022-04-20T22:04:42.23Z" },
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "mdurl" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" },
+ { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" },
+ { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" },
+ { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" },
+ { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" },
+ { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
+ { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
+ { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
+ { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
+ { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
+ { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
+ { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
+ { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
+ { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
+ { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
+ { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
+ { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
+ { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
+ { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
+ { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
+ { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
+ { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
+ { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
+ { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
+ { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.10.8"
+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/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" },
+ { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" },
+ { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" },
+ { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/b9/15fd5541ef4f5b9a17eefd379356cf12175fe577424e7b1d80676516031a/matplotlib-3.10.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3f2e409836d7f5ac2f1c013110a4d50b9f7edc26328c108915f9075d7d7a91b6", size = 8261076, upload-time = "2025-12-10T22:55:44.648Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/a0/2ba3473c1b66b9c74dc7107c67e9008cb1782edbe896d4c899d39ae9cf78/matplotlib-3.10.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56271f3dac49a88d7fca5060f004d9d22b865f743a12a23b1e937a0be4818ee1", size = 8148794, upload-time = "2025-12-10T22:55:46.252Z" },
+ { url = "https://files.pythonhosted.org/packages/75/97/a471f1c3eb1fd6f6c24a31a5858f443891d5127e63a7788678d14e249aea/matplotlib-3.10.8-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0a7f52498f72f13d4a25ea70f35f4cb60642b466cbb0a9be951b5bc3f45a486", size = 8718474, upload-time = "2025-12-10T22:55:47.864Z" },
+ { url = "https://files.pythonhosted.org/packages/01/be/cd478f4b66f48256f42927d0acbcd63a26a893136456cd079c0cc24fbabf/matplotlib-3.10.8-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:646d95230efb9ca614a7a594d4fcacde0ac61d25e37dd51710b36477594963ce", size = 9549637, upload-time = "2025-12-10T22:55:50.048Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/7c/8dc289776eae5109e268c4fb92baf870678dc048a25d4ac903683b86d5bf/matplotlib-3.10.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f89c151aab2e2e23cb3fe0acad1e8b82841fd265379c4cecd0f3fcb34c15e0f6", size = 9613678, upload-time = "2025-12-10T22:55:52.21Z" },
+ { url = "https://files.pythonhosted.org/packages/64/40/37612487cc8a437d4dd261b32ca21fe2d79510fe74af74e1f42becb1bdb8/matplotlib-3.10.8-cp313-cp313-win_amd64.whl", hash = "sha256:e8ea3e2d4066083e264e75c829078f9e149fa119d27e19acd503de65e0b13149", size = 8142686, upload-time = "2025-12-10T22:55:54.253Z" },
+ { url = "https://files.pythonhosted.org/packages/66/52/8d8a8730e968185514680c2a6625943f70269509c3dcfc0dcf7d75928cb8/matplotlib-3.10.8-cp313-cp313-win_arm64.whl", hash = "sha256:c108a1d6fa78a50646029cb6d49808ff0fc1330fda87fa6f6250c6b5369b6645", size = 8012917, upload-time = "2025-12-10T22:55:56.268Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/27/51fe26e1062f298af5ef66343d8ef460e090a27fea73036c76c35821df04/matplotlib-3.10.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ad3d9833a64cf48cc4300f2b406c3d0f4f4724a91c0bd5640678a6ba7c102077", size = 8305679, upload-time = "2025-12-10T22:55:57.856Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/1e/4de865bc591ac8e3062e835f42dd7fe7a93168d519557837f0e37513f629/matplotlib-3.10.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:eb3823f11823deade26ce3b9f40dcb4a213da7a670013929f31d5f5ed1055b22", size = 8198336, upload-time = "2025-12-10T22:55:59.371Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/cb/2f7b6e75fb4dce87ef91f60cac4f6e34f4c145ab036a22318ec837971300/matplotlib-3.10.8-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d9050fee89a89ed57b4fb2c1bfac9a3d0c57a0d55aed95949eedbc42070fea39", size = 8731653, upload-time = "2025-12-10T22:56:01.032Z" },
+ { url = "https://files.pythonhosted.org/packages/46/b3/bd9c57d6ba670a37ab31fb87ec3e8691b947134b201f881665b28cc039ff/matplotlib-3.10.8-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b44d07310e404ba95f8c25aa5536f154c0a8ec473303535949e52eb71d0a1565", size = 9561356, upload-time = "2025-12-10T22:56:02.95Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/3d/8b94a481456dfc9dfe6e39e93b5ab376e50998cddfd23f4ae3b431708f16/matplotlib-3.10.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0a33deb84c15ede243aead39f77e990469fff93ad1521163305095b77b72ce4a", size = 9614000, upload-time = "2025-12-10T22:56:05.411Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/cd/bc06149fe5585ba800b189a6a654a75f1f127e8aab02fd2be10df7fa500c/matplotlib-3.10.8-cp313-cp313t-win_amd64.whl", hash = "sha256:3a48a78d2786784cc2413e57397981fb45c79e968d99656706018d6e62e57958", size = 8220043, upload-time = "2025-12-10T22:56:07.551Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/de/b22cf255abec916562cc04eef457c13e58a1990048de0c0c3604d082355e/matplotlib-3.10.8-cp313-cp313t-win_arm64.whl", hash = "sha256:15d30132718972c2c074cd14638c7f4592bd98719e2308bccea40e0538bc0cb5", size = 8062075, upload-time = "2025-12-10T22:56:09.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/43/9c0ff7a2f11615e516c3b058e1e6e8f9614ddeca53faca06da267c48345d/matplotlib-3.10.8-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b53285e65d4fa4c86399979e956235deb900be5baa7fc1218ea67fbfaeaadd6f", size = 8262481, upload-time = "2025-12-10T22:56:10.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ca/e8ae28649fcdf039fda5ef554b40a95f50592a3c47e6f7270c9561c12b07/matplotlib-3.10.8-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32f8dce744be5569bebe789e46727946041199030db8aeb2954d26013a0eb26b", size = 8151473, upload-time = "2025-12-10T22:56:12.377Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/6f/009d129ae70b75e88cbe7e503a12a4c0670e08ed748a902c2568909e9eb5/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4cf267add95b1c88300d96ca837833d4112756045364f5c734a2276038dae27d", size = 9553896, upload-time = "2025-12-10T22:56:14.432Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/26/4221a741eb97967bc1fd5e4c52b9aa5a91b2f4ec05b59f6def4d820f9df9/matplotlib-3.10.8-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2cf5bd12cecf46908f286d7838b2abc6c91cda506c0445b8223a7c19a00df008", size = 9824193, upload-time = "2025-12-10T22:56:16.29Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/f3/3abf75f38605772cf48a9daf5821cd4f563472f38b4b828c6fba6fa6d06e/matplotlib-3.10.8-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:41703cc95688f2516b480f7f339d8851a6035f18e100ee6a32bc0b8536a12a9c", size = 9615444, upload-time = "2025-12-10T22:56:18.155Z" },
+ { url = "https://files.pythonhosted.org/packages/93/a5/de89ac80f10b8dc615807ee1133cd99ac74082581196d4d9590bea10690d/matplotlib-3.10.8-cp314-cp314-win_amd64.whl", hash = "sha256:83d282364ea9f3e52363da262ce32a09dfe241e4080dcedda3c0db059d3c1f11", size = 8272719, upload-time = "2025-12-10T22:56:20.366Z" },
+ { url = "https://files.pythonhosted.org/packages/69/ce/b006495c19ccc0a137b48083168a37bd056392dee02f87dba0472f2797fe/matplotlib-3.10.8-cp314-cp314-win_arm64.whl", hash = "sha256:2c1998e92cd5999e295a731bcb2911c75f597d937341f3030cc24ef2733d78a8", size = 8144205, upload-time = "2025-12-10T22:56:22.239Z" },
+ { url = "https://files.pythonhosted.org/packages/68/d9/b31116a3a855bd313c6fcdb7226926d59b041f26061c6c5b1be66a08c826/matplotlib-3.10.8-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b5a2b97dbdc7d4f353ebf343744f1d1f1cca8aa8bfddb4262fcf4306c3761d50", size = 8305785, upload-time = "2025-12-10T22:56:24.218Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/90/6effe8103f0272685767ba5f094f453784057072f49b393e3ea178fe70a5/matplotlib-3.10.8-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3f5c3e4da343bba819f0234186b9004faba952cc420fbc522dc4e103c1985908", size = 8198361, upload-time = "2025-12-10T22:56:26.787Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/65/a73188711bea603615fc0baecca1061429ac16940e2385433cc778a9d8e7/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f62550b9a30afde8c1c3ae450e5eb547d579dd69b25c2fc7a1c67f934c1717a", size = 9561357, upload-time = "2025-12-10T22:56:28.953Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/3d/b5c5d5d5be8ce63292567f0e2c43dde9953d3ed86ac2de0a72e93c8f07a1/matplotlib-3.10.8-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:495672de149445ec1b772ff2c9ede9b769e3cb4f0d0aa7fa730d7f59e2d4e1c1", size = 9823610, upload-time = "2025-12-10T22:56:31.455Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/4b/e7beb6bbd49f6bae727a12b270a2654d13c397576d25bd6786e47033300f/matplotlib-3.10.8-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:595ba4d8fe983b88f0eec8c26a241e16d6376fe1979086232f481f8f3f67494c", size = 9614011, upload-time = "2025-12-10T22:56:33.85Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/e6/76f2813d31f032e65f6f797e3f2f6e4aab95b65015924b1c51370395c28a/matplotlib-3.10.8-cp314-cp314t-win_amd64.whl", hash = "sha256:25d380fe8b1dc32cf8f0b1b448470a77afb195438bafdf1d858bfb876f3edf7b", size = 8362801, upload-time = "2025-12-10T22:56:36.107Z" },
+ { url = "https://files.pythonhosted.org/packages/5d/49/d651878698a0b67f23aa28e17f45a6d6dd3d3f933fa29087fa4ce5947b5a/matplotlib-3.10.8-cp314-cp314t-win_arm64.whl", hash = "sha256:113bb52413ea508ce954a02c10ffd0d565f9c3bc7f2eddc27dfe1731e71c7b5f", size = 8192560, upload-time = "2025-12-10T22:56:38.008Z" },
+ { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" },
+ { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" },
+]
+
+[[package]]
+name = "matplotlib-inline"
+version = "0.2.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" },
+]
+
+[[package]]
+name = "mdit-py-plugins"
+version = "0.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "markdown-it-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b2/fd/a756d36c0bfba5f6e39a1cdbdbfdd448dc02692467d83816dff4592a1ebc/mdit_py_plugins-0.5.0.tar.gz", hash = "sha256:f4918cb50119f50446560513a8e311d574ff6aaed72606ddae6d35716fe809c6", size = 44655, upload-time = "2025-08-11T07:25:49.083Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/86/dd6e5db36df29e76c7a7699123569a4a18c1623ce68d826ed96c62643cae/mdit_py_plugins-0.5.0-py3-none-any.whl", hash = "sha256:07a08422fc1936a5d26d146759e9155ea466e842f5ab2f7d2266dd084c8dab1f", size = 57205, upload-time = "2025-08-11T07:25:47.597Z" },
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
+]
+
+[[package]]
+name = "mistune"
+version = "3.2.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" },
+]
+
+[[package]]
+name = "mypy"
+version = "1.20.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "librt", marker = "platform_python_implementation != 'PyPy'" },
+ { name = "mypy-extensions" },
+ { name = "pathspec" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/af/e3d4b3e9ec91a0ff9aabfdb38692952acf49bbb899c2e4c29acb3a6da3ae/mypy-1.20.2.tar.gz", hash = "sha256:e8222c26daaafd9e8626dec58ae36029f82585890589576f769a650dd20fd665", size = 3817349, upload-time = "2026-04-21T17:12:28.473Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1f/4d/9ebeae211caccbdaddde7ed5e31dfcf57faac66be9b11deb1dc6526c8078/mypy-1.20.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4077797a273e56e8843d001e9dfe4ba10e33323d6ade647ff260e5cd97d9758c", size = 14371307, upload-time = "2026-04-21T17:08:56.442Z" },
+ { url = "https://files.pythonhosted.org/packages/95/d7/93473d34b61f04fac1aecc01368485c89c5c4af7a4b9a0cab5d77d04b63f/mypy-1.20.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cdecf62abcc4292500d7858aeae87a1f8f1150f4c4dd08fb0b336ee79b2a6df3", size = 13258917, upload-time = "2026-04-21T17:05:50.978Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/30/3dd903e8bafb7b5f7bf87fcd58f8382086dea2aa19f0a7b357f21f63071b/mypy-1.20.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c566c3a88b6ece59b3d70f65bedef17304f48eb52ff040a6a18214e1917b3254", size = 13700516, upload-time = "2026-04-21T17:11:33.161Z" },
+ { url = "https://files.pythonhosted.org/packages/07/05/c61a140aba4c729ac7bc99ae26fc627c78a6e08f5b9dd319244ea71a3d7e/mypy-1.20.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0deb80d062b2479f2c87ae568f89845afc71d11bc41b04179e58165fd9f31e98", size = 14562889, upload-time = "2026-04-21T17:05:27.674Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/87/da78243742ffa8a36d98c3010f0d829f93d5da4e6786f1a1a6f2ad616502/mypy-1.20.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bba9ad231e92a3e424b3e56b65aa17704993425bba97e302c832f9466bb85bac", size = 14803844, upload-time = "2026-04-21T17:10:06.2Z" },
+ { url = "https://files.pythonhosted.org/packages/37/52/10a1ddf91b40f843943a3c6db51e2df59c9e237f29d355e95eaab427461f/mypy-1.20.2-cp311-cp311-win_amd64.whl", hash = "sha256:baf593f2765fa3a6b1ef95807dbaa3d25b594f6a52adcc506a6b9cb115e1be67", size = 10846300, upload-time = "2026-04-21T17:12:23.886Z" },
+ { url = "https://files.pythonhosted.org/packages/20/02/f9a4415b664c53bd34d6709be59da303abcae986dc4ac847b402edb6fa1e/mypy-1.20.2-cp311-cp311-win_arm64.whl", hash = "sha256:20175a1c0f49863946ec20b7f63255768058ac4f07d2b9ded6a6b46cfb5a9100", size = 9779498, upload-time = "2026-04-21T17:09:23.695Z" },
+ { url = "https://files.pythonhosted.org/packages/71/4e/7560e4528db9e9b147e4c0f22660466bf30a0a1fe3d63d1b9d3b0fd354ee/mypy-1.20.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4dbfcf869f6b0517f70cf0030ba6ea1d6645e132337a7d5204a18d8d5636c02b", size = 14539393, upload-time = "2026-04-21T17:07:12.52Z" },
+ { url = "https://files.pythonhosted.org/packages/32/d9/34a5efed8124f5a9234f55ac6a4ced4201e2c5b81e1109c49ad23190ec8c/mypy-1.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b6481b228d072315b053210b01ac320e1be243dc17f9e5887ef167f23f5fae4", size = 13361642, upload-time = "2026-04-21T17:06:53.742Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/14/eb377acf78c03c92d566a1510cda8137348215b5335085ef662ab82ecd3a/mypy-1.20.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:34397cdced6b90b836e38182076049fdb41424322e0b0728c946b0939ebdf9f6", size = 13740347, upload-time = "2026-04-21T17:12:04.73Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/94/7e4634a32b641aa1c112422eed1bbece61ee16205f674190e8b536f884de/mypy-1.20.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5da6976f20cae27059ea8d0c86e7cef3de720e04c4bb9ee18e3690fdb792066", size = 14734042, upload-time = "2026-04-21T17:07:43.16Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/f3/f7e62395cb7f434541b4491a01149a4439e28ace4c0c632bbf5431e92d1f/mypy-1.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:56908d7e08318d39f85b1f0c6cfd47b0cac1a130da677630dac0de3e0623e102", size = 14964958, upload-time = "2026-04-21T17:11:00.665Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/0d/47e3c3a0ec2a876e35aeac365df3cac7776c36bbd4ed18cc521e1b9d255b/mypy-1.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:d52ad8d78522da1d308789df651ee5379088e77c76cb1994858d40a426b343b9", size = 10911340, upload-time = "2026-04-21T17:10:49.179Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/b2/6c852d72e0ea8b01f49da817fb52539993cde327e7d010e0103dc12d0dac/mypy-1.20.2-cp312-cp312-win_arm64.whl", hash = "sha256:785b08db19c9f214dc37d65f7c165d19a30fcecb48abfa30f31b01b5acaabb58", size = 9833947, upload-time = "2026-04-21T17:09:05.267Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/c4/b93812d3a192c9bcf5df405bd2f30277cd0e48106a14d1023c7f6ed6e39b/mypy-1.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:edfbfca868cdd6bd8d974a60f8a3682f5565d3f5c99b327640cedd24c4264026", size = 14524670, upload-time = "2026-04-21T17:10:30.737Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/47/42c122501bff18eaf1e8f457f5c017933452d8acdc52918a9f59f6812955/mypy-1.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e2877a02380adfcdbc69071a0f74d6e9dbbf593c0dc9d174e1f223ffd5281943", size = 13336218, upload-time = "2026-04-21T17:08:44.069Z" },
+ { url = "https://files.pythonhosted.org/packages/92/8f/75bbc92f41725fbd585fb17b440b1119b576105df1013622983e18640a93/mypy-1.20.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7488448de6007cd5177c6cea0517ac33b4c0f5ee9b5e9f2be51ce75511a85517", size = 13724906, upload-time = "2026-04-21T17:08:01.02Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/32/4c49da27a606167391ff0c39aa955707a00edc500572e562f7c36c08a71f/mypy-1.20.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb9c2fa06887e21d6a3a868762acb82aec34e2c6fd0174064f27c93ede68ad15", size = 14726046, upload-time = "2026-04-21T17:11:22.354Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/fc/4e354a1bd70216359deb0c9c54847ee6b32ef78dfb09f5131ff99b494078/mypy-1.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d56a78b646f2e3daa865bc70cd5ec5a46c50045801ca8ff17a0c43abc97e3ee", size = 14955587, upload-time = "2026-04-21T17:12:16.033Z" },
+ { url = "https://files.pythonhosted.org/packages/62/b2/c0f2056e9eb8f08c62cafd9715e4584b89132bdc832fcf85d27d07b5f3e5/mypy-1.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:2a4102b03bb7481d9a91a6da8d174740c9c8c4401024684b9ca3b7cc5e49852f", size = 10922681, upload-time = "2026-04-21T17:06:35.842Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/14/065e333721f05de8ef683d0aa804c23026bcc287446b61cac657b902ccac/mypy-1.20.2-cp313-cp313-win_arm64.whl", hash = "sha256:a95a9248b0c6fd933a442c03c3b113c3b61320086b88e2c444676d3fd1ca3330", size = 9830560, upload-time = "2026-04-21T17:07:51.023Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/d1/b4ec96b0ecc620a4443570c6e95c867903428cfcde4206518eafdd5880c3/mypy-1.20.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:419413398fe250aae057fd2fe50166b61077083c9b82754c341cf4fd73038f30", size = 14524561, upload-time = "2026-04-21T17:06:27.325Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/63/d2c2ff4fa66bc49477d32dfa26e8a167ba803ea6a69c5efb416036909d30/mypy-1.20.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e73c07f23009962885c197ccb9b41356a30cc0e5a1d0c2ea8fd8fb1362d7f924", size = 13363883, upload-time = "2026-04-21T17:11:11.239Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/56/983916806bf4eddeaaa2c9230903c3669c6718552a921154e1c5182c701f/mypy-1.20.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c64e5973df366b747646fc98da921f9d6eba9716d57d1db94a83c026a08e0fb", size = 13742945, upload-time = "2026-04-21T17:08:34.181Z" },
+ { url = "https://files.pythonhosted.org/packages/19/65/0cd9285ab010ee8214c83d67c6b49417c40d86ce46f1aa109457b5a9b8d7/mypy-1.20.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a65aa591af023864fd08a97da9974e919452cfe19cb146c8a5dc692626445dc", size = 14706163, upload-time = "2026-04-21T17:05:15.51Z" },
+ { url = "https://files.pythonhosted.org/packages/94/97/48ff3b297cafcc94d185243a9190836fb1b01c1b0918fff64e941e973cc9/mypy-1.20.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4fef51b01e638974a6e69885687e9bd40c8d1e09a6cd291cca0619625cf1f558", size = 14938677, upload-time = "2026-04-21T17:05:39.562Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/a1/1b4233d255bdd0b38a1f284feeb1c143ca508c19184964e22f8d837ec851/mypy-1.20.2-cp314-cp314-win_amd64.whl", hash = "sha256:913485a03f1bcf5d279409a9d2b9ed565c151f61c09f29991e5faa14033da4c8", size = 11089322, upload-time = "2026-04-21T17:06:44.29Z" },
+ { url = "https://files.pythonhosted.org/packages/78/c2/ce7ee2ba36aeb954ba50f18fa25d9c1188578654b97d02a66a15b6f09531/mypy-1.20.2-cp314-cp314-win_arm64.whl", hash = "sha256:c3bae4f855d965b5453784300c12ffc63a548304ac7f99e55d4dc7c898673aa3", size = 10017775, upload-time = "2026-04-21T17:07:20.732Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/a1/9d93a7d0b5859af0ead82b4888b46df6c8797e1bc5e1e262a08518c6d48e/mypy-1.20.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:2de3dcea53babc1c3237a19002bc3d228ce1833278f093b8d619e06e7cc79609", size = 15549002, upload-time = "2026-04-21T17:08:23.107Z" },
+ { url = "https://files.pythonhosted.org/packages/00/d2/09a6a10ee1bf0008f6c144d9676f2ca6a12512151b4e0ad0ff6c4fac5337/mypy-1.20.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:52b176444e2e5054dfcbcb8c75b0b719865c96247b37407184bbfca5c353f2c2", size = 14401942, upload-time = "2026-04-21T17:07:31.837Z" },
+ { url = "https://files.pythonhosted.org/packages/57/da/9594b75c3c019e805250bed3583bdf4443ff9e6ef08f97e39ae308cb06f2/mypy-1.20.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:688c3312e5dadb573a2c69c82af3a298d43ecf9e6d264e0f95df960b5f6ac19c", size = 15041649, upload-time = "2026-04-21T17:09:34.653Z" },
+ { url = "https://files.pythonhosted.org/packages/97/77/f75a65c278e6e8eba2071f7f5a90481891053ecc39878cc444634d892abe/mypy-1.20.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29752dbbf8cc53f89f6ac096d363314333045c257c9c75cbd189ca2de0455744", size = 15864588, upload-time = "2026-04-21T17:11:44.936Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/46/1a4e1c66e96c1a3246ddf5403d122ac9b0a8d2b7e65730b9d6533ba7a6d3/mypy-1.20.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:803203d2b6ea644982c644895c2f78b28d0e208bba7b27d9b921e0ec5eb207c6", size = 16093956, upload-time = "2026-04-21T17:10:17.683Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/2c/78a8851264dec38cd736ca5b8bc9380674df0dd0be7792f538916157716c/mypy-1.20.2-cp314-cp314t-win_amd64.whl", hash = "sha256:9bcb8aa397ff0093c824182fd76a935a9ba7ad097fcbef80ae89bf6c1731d8ec", size = 12568661, upload-time = "2026-04-21T17:11:54.473Z" },
+ { url = "https://files.pythonhosted.org/packages/83/01/cd7318aa03493322ce275a0e14f4f52b8896335e4e79d4fb8153a7ad2b77/mypy-1.20.2-cp314-cp314t-win_arm64.whl", hash = "sha256:e061b58443f1736f8a37c48978d7ab581636d6ab03e3d4f99e3fa90463bb9382", size = 10389240, upload-time = "2026-04-21T17:09:42.719Z" },
+ { url = "https://files.pythonhosted.org/packages/28/9a/f23c163e25b11074188251b0b5a0342625fc1cdb6af604757174fa9acc9b/mypy-1.20.2-py3-none-any.whl", hash = "sha256:a94c5a76ab46c5e6257c7972b6c8cff0574201ca7dc05647e33e795d78680563", size = 2637314, upload-time = "2026-04-21T17:05:54.5Z" },
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
+]
+
+[[package]]
+name = "myst-parser"
+version = "5.0.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "jinja2" },
+ { name = "markdown-it-py" },
+ { name = "mdit-py-plugins" },
+ { name = "pyyaml" },
+ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/33/fa/7b45eef11b7971f0beb29d27b7bfe0d747d063aa29e170d9edd004733c8a/myst_parser-5.0.0.tar.gz", hash = "sha256:f6f231452c56e8baa662cc352c548158f6a16fcbd6e3800fc594978002b94f3a", size = 98535, upload-time = "2026-01-15T09:08:18.036Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d3/ac/686789b9145413f1a61878c407210e41bfdb097976864e0913078b24098c/myst_parser-5.0.0-py3-none-any.whl", hash = "sha256:ab31e516024918296e169139072b81592336f2fef55b8986aa31c9f04b5f7211", size = 84533, upload-time = "2026-01-15T09:08:16.788Z" },
+]
+
+[[package]]
+name = "nbclient"
+version = "0.10.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "jupyter-client" },
+ { name = "jupyter-core" },
+ { name = "nbformat" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" },
+]
+
+[[package]]
+name = "nbconvert"
+version = "7.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "beautifulsoup4" },
+ { name = "bleach", extra = ["css"] },
+ { name = "defusedxml" },
+ { name = "jinja2" },
+ { name = "jupyter-core" },
+ { name = "jupyterlab-pygments" },
+ { name = "markupsafe" },
+ { name = "mistune" },
+ { name = "nbclient" },
+ { name = "nbformat" },
+ { name = "packaging" },
+ { name = "pandocfilters" },
+ { name = "pygments" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/01/b1/708e53fe2e429c103c6e6e159106bcf0357ac41aa4c28772bd8402339051/nbconvert-7.17.1.tar.gz", hash = "sha256:34d0d0a7e73ce3cbab6c5aae8f4f468797280b01fd8bd2ca746da8569eddd7d2", size = 865311, upload-time = "2026-04-08T00:44:14.914Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/67/f8/bb0a9d5f46819c821dc1f004aa2cc29b1d91453297dbf5ff20470f00f193/nbconvert-7.17.1-py3-none-any.whl", hash = "sha256:aa85c087b435e7bf1ffd03319f658e285f2b89eccab33bc1ba7025495ab3e7c8", size = 261927, upload-time = "2026-04-08T00:44:12.845Z" },
+]
+
+[[package]]
+name = "nbformat"
+version = "5.10.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "fastjsonschema" },
+ { name = "jsonschema" },
+ { name = "jupyter-core" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" },
+]
+
+[[package]]
+name = "nbsphinx"
+version = "0.9.8"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "docutils" },
+ { name = "jinja2" },
+ { name = "nbconvert" },
+ { name = "nbformat" },
+ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "traitlets" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e7/d1/82081750f8a78ad0399c6ed831d42623b891904e8e7b8a75878225cf1dce/nbsphinx-0.9.8.tar.gz", hash = "sha256:d0765908399a8ee2b57be7ae881cf2ea58d66db3af7bbf33e6eb48f83bea5495", size = 417469, upload-time = "2025-11-28T17:41:02.336Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/03/78/843bcf0cf31f88d2f8a9a063d2d80817b1901657d83d65b89b3aa835732e/nbsphinx-0.9.8-py3-none-any.whl", hash = "sha256:92d95ee91784e56bc633b60b767a6b6f23a0445f891e24641ce3c3f004759ccf", size = 31961, upload-time = "2025-11-28T17:41:00.796Z" },
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
+]
+
+[[package]]
+name = "networkx"
+version = "3.6.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.4.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f9/51/5093a2df15c4dc19da3f79d1021e891f5dcf1d9d1db6ba38891d5590f3fe/numpy-2.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:33b3bf58ee84b172c067f56aeadc7ee9ab6de69c5e800ab5b10295d54c581adb", size = 16957183, upload-time = "2026-03-09T07:55:57.774Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/7c/c061f3de0630941073d2598dc271ac2f6cbcf5c83c74a5870fea07488333/numpy-2.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ba7b51e71c05aa1f9bc3641463cd82308eab40ce0d5c7e1fd4038cbf9938147", size = 14968734, upload-time = "2026-03-09T07:56:00.494Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/27/d26c85cbcd86b26e4f125b0668e7a7c0542d19dd7d23ee12e87b550e95b5/numpy-2.4.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a1988292870c7cb9d0ebb4cc96b4d447513a9644801de54606dc7aabf2b7d920", size = 5475288, upload-time = "2026-03-09T07:56:02.857Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/09/3c4abbc1dcd8010bf1a611d174c7aa689fc505585ec806111b4406f6f1b1/numpy-2.4.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:23b46bb6d8ecb68b58c09944483c135ae5f0e9b8d8858ece5e4ead783771d2a9", size = 6805253, upload-time = "2026-03-09T07:56:04.53Z" },
+ { url = "https://files.pythonhosted.org/packages/21/bc/e7aa3f6817e40c3f517d407742337cbb8e6fc4b83ce0b55ab780c829243b/numpy-2.4.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a016db5c5dba78fa8fe9f5d80d6708f9c42ab087a739803c0ac83a43d686a470", size = 15969479, upload-time = "2026-03-09T07:56:06.638Z" },
+ { url = "https://files.pythonhosted.org/packages/78/51/9f5d7a41f0b51649ddf2f2320595e15e122a40610b233d51928dd6c92353/numpy-2.4.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:715de7f82e192e8cae5a507a347d97ad17598f8e026152ca97233e3666daaa71", size = 16901035, upload-time = "2026-03-09T07:56:09.405Z" },
+ { url = "https://files.pythonhosted.org/packages/64/6e/b221dd847d7181bc5ee4857bfb026182ef69499f9305eb1371cbb1aea626/numpy-2.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ddb7919366ee468342b91dea2352824c25b55814a987847b6c52003a7c97f15", size = 17325657, upload-time = "2026-03-09T07:56:12.067Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/b8/8f3fd2da596e1063964b758b5e3c970aed1949a05200d7e3d46a9d46d643/numpy-2.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a315e5234d88067f2d97e1f2ef670a7569df445d55400f1e33d117418d008d52", size = 18635512, upload-time = "2026-03-09T07:56:14.629Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/24/2993b775c37e39d2f8ab4125b44337ab0b2ba106c100980b7c274a22bee7/numpy-2.4.3-cp311-cp311-win32.whl", hash = "sha256:2b3f8d2c4589b1a2028d2a770b0fc4d1f332fb5e01521f4de3199a896d158ddd", size = 6238100, upload-time = "2026-03-09T07:56:17.243Z" },
+ { url = "https://files.pythonhosted.org/packages/76/1d/edccf27adedb754db7c4511d5eac8b83f004ae948fe2d3509e8b78097d4c/numpy-2.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:77e76d932c49a75617c6d13464e41203cd410956614d0a0e999b25e9e8d27eec", size = 12609816, upload-time = "2026-03-09T07:56:19.089Z" },
+ { url = "https://files.pythonhosted.org/packages/92/82/190b99153480076c8dce85f4cfe7d53ea84444145ffa54cb58dcd460d66b/numpy-2.4.3-cp311-cp311-win_arm64.whl", hash = "sha256:eb610595dd91560905c132c709412b512135a60f1851ccbd2c959e136431ff67", size = 10485757, upload-time = "2026-03-09T07:56:21.753Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" },
+ { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" },
+ { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/d0/1fe47a98ce0df229238b77611340aff92d52691bcbc10583303181abf7fc/numpy-2.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b346845443716c8e542d54112966383b448f4a3ba5c66409771b8c0889485dd3", size = 16665297, upload-time = "2026-03-09T07:56:52.296Z" },
+ { url = "https://files.pythonhosted.org/packages/27/d9/4e7c3f0e68dfa91f21c6fb6cf839bc829ec920688b1ce7ec722b1a6202fb/numpy-2.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2629289168f4897a3c4e23dc98d6f1731f0fc0fe52fb9db19f974041e4cc12b9", size = 14691853, upload-time = "2026-03-09T07:56:54.992Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/66/bd096b13a87549683812b53ab211e6d413497f84e794fb3c39191948da97/numpy-2.4.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bb2e3cf95854233799013779216c57e153c1ee67a0bf92138acca0e429aefaee", size = 5198435, upload-time = "2026-03-09T07:56:57.184Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/2f/687722910b5a5601de2135c891108f51dfc873d8e43c8ed9f4ebb440b4a2/numpy-2.4.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:7f3408ff897f8ab07a07fbe2823d7aee6ff644c097cc1f90382511fe982f647f", size = 6546347, upload-time = "2026-03-09T07:56:59.531Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/ec/7971c4e98d86c564750393fab8d7d83d0a9432a9d78bb8a163a6dc59967a/numpy-2.4.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:decb0eb8a53c3b009b0962378065589685d66b23467ef5dac16cbe818afde27f", size = 15664626, upload-time = "2026-03-09T07:57:01.385Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/eb/7daecbea84ec935b7fc732e18f532073064a3816f0932a40a17f3349185f/numpy-2.4.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5f51900414fc9204a0e0da158ba2ac52b75656e7dce7e77fb9f84bfa343b4cc", size = 16608916, upload-time = "2026-03-09T07:57:04.008Z" },
+ { url = "https://files.pythonhosted.org/packages/df/58/2a2b4a817ffd7472dca4421d9f0776898b364154e30c95f42195041dc03b/numpy-2.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6bd06731541f89cdc01b261ba2c9e037f1543df7472517836b78dfb15bd6e476", size = 17015824, upload-time = "2026-03-09T07:57:06.347Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/ca/627a828d44e78a418c55f82dd4caea8ea4a8ef24e5144d9e71016e52fb40/numpy-2.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:22654fe6be0e5206f553a9250762c653d3698e46686eee53b399ab90da59bd92", size = 18334581, upload-time = "2026-03-09T07:57:09.114Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/c0/76f93962fc79955fcba30a429b62304332345f22d4daec1cb33653425643/numpy-2.4.3-cp313-cp313-win32.whl", hash = "sha256:d71e379452a2f670ccb689ec801b1218cd3983e253105d6e83780967e899d687", size = 5958618, upload-time = "2026-03-09T07:57:11.432Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/3c/88af0040119209b9b5cb59485fa48b76f372c73068dbf9254784b975ac53/numpy-2.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:0a60e17a14d640f49146cb38e3f105f571318db7826d9b6fef7e4dce758faecd", size = 12312824, upload-time = "2026-03-09T07:57:13.586Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ce/3d07743aced3d173f877c3ef6a454c2174ba42b584ab0b7e6d99374f51ed/numpy-2.4.3-cp313-cp313-win_arm64.whl", hash = "sha256:c9619741e9da2059cd9c3f206110b97583c7152c1dc9f8aafd4beb450ac1c89d", size = 10221218, upload-time = "2026-03-09T07:57:16.183Z" },
+ { url = "https://files.pythonhosted.org/packages/62/09/d96b02a91d09e9d97862f4fc8bfebf5400f567d8eb1fe4b0cc4795679c15/numpy-2.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7aa4e54f6469300ebca1d9eb80acd5253cdfa36f2c03d79a35883687da430875", size = 14819570, upload-time = "2026-03-09T07:57:18.564Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/ca/0b1aba3905fdfa3373d523b2b15b19029f4f3031c87f4066bd9d20ef6c6b/numpy-2.4.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d1b90d840b25874cf5cd20c219af10bac3667db3876d9a495609273ebe679070", size = 5326113, upload-time = "2026-03-09T07:57:21.052Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/63/406e0fd32fcaeb94180fd6a4c41e55736d676c54346b7efbce548b94a914/numpy-2.4.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a749547700de0a20a6718293396ec237bb38218049cfce788e08fcb716e8cf73", size = 6646370, upload-time = "2026-03-09T07:57:22.804Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/d0/10f7dc157d4b37af92720a196be6f54f889e90dcd30dce9dc657ed92c257/numpy-2.4.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f3c4a151a2e529adf49c1d54f0f57ff8f9b233ee4d44af623a81553ab86368", size = 15723499, upload-time = "2026-03-09T07:57:24.693Z" },
+ { url = "https://files.pythonhosted.org/packages/66/f1/d1c2bf1161396629701bc284d958dc1efa3a5a542aab83cf11ee6eb4cba5/numpy-2.4.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22c31dc07025123aedf7f2db9e91783df13f1776dc52c6b22c620870dc0fab22", size = 16657164, upload-time = "2026-03-09T07:57:27.676Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/be/cca19230b740af199ac47331a21c71e7a3d0ba59661350483c1600d28c37/numpy-2.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:148d59127ac95979d6f07e4d460f934ebdd6eed641db9c0db6c73026f2b2101a", size = 17081544, upload-time = "2026-03-09T07:57:30.664Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/c5/9602b0cbb703a0936fb40f8a95407e8171935b15846de2f0776e08af04c7/numpy-2.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a97cbf7e905c435865c2d939af3d93f99d18eaaa3cabe4256f4304fb51604349", size = 18380290, upload-time = "2026-03-09T07:57:33.763Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/81/9f24708953cd30be9ee36ec4778f4b112b45165812f2ada4cc5ea1c1f254/numpy-2.4.3-cp313-cp313t-win32.whl", hash = "sha256:be3b8487d725a77acccc9924f65fd8bce9af7fac8c9820df1049424a2115af6c", size = 6082814, upload-time = "2026-03-09T07:57:36.491Z" },
+ { url = "https://files.pythonhosted.org/packages/e2/9e/52f6eaa13e1a799f0ab79066c17f7016a4a8ae0c1aefa58c82b4dab690b4/numpy-2.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1ec84fd7c8e652b0f4aaaf2e6e9cc8eaa9b1b80a537e06b2e3a2fb176eedcb26", size = 12452673, upload-time = "2026-03-09T07:57:38.281Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/04/b8cece6ead0b30c9fbd99bb835ad7ea0112ac5f39f069788c5558e3b1ab2/numpy-2.4.3-cp313-cp313t-win_arm64.whl", hash = "sha256:120df8c0a81ebbf5b9020c91439fccd85f5e018a927a39f624845be194a2be02", size = 10290907, upload-time = "2026-03-09T07:57:40.747Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ae/3936f79adebf8caf81bd7a599b90a561334a658be4dcc7b6329ebf4ee8de/numpy-2.4.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5884ce5c7acfae1e4e1b6fde43797d10aa506074d25b531b4f54bde33c0c31d4", size = 16664563, upload-time = "2026-03-09T07:57:43.817Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/62/760f2b55866b496bb1fa7da2a6db076bef908110e568b02fcfc1422e2a3a/numpy-2.4.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:297837823f5bc572c5f9379b0c9f3a3365f08492cbdc33bcc3af174372ebb168", size = 14702161, upload-time = "2026-03-09T07:57:46.169Z" },
+ { url = "https://files.pythonhosted.org/packages/32/af/a7a39464e2c0a21526fb4fb76e346fb172ebc92f6d1c7a07c2c139cc17b1/numpy-2.4.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:a111698b4a3f8dcbe54c64a7708f049355abd603e619013c346553c1fd4ca90b", size = 5208738, upload-time = "2026-03-09T07:57:48.506Z" },
+ { url = "https://files.pythonhosted.org/packages/29/8c/2a0cf86a59558fa078d83805589c2de490f29ed4fb336c14313a161d358a/numpy-2.4.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:4bd4741a6a676770e0e97fe9ab2e51de01183df3dcbcec591d26d331a40de950", size = 6543618, upload-time = "2026-03-09T07:57:50.591Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/b8/612ce010c0728b1c363fa4ea3aa4c22fe1c5da1de008486f8c2f5cb92fae/numpy-2.4.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54f29b877279d51e210e0c80709ee14ccbbad647810e8f3d375561c45ef613dd", size = 15680676, upload-time = "2026-03-09T07:57:52.34Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/7e/4f120ecc54ba26ddf3dc348eeb9eb063f421de65c05fc961941798feea18/numpy-2.4.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:679f2a834bae9020f81534671c56fd0cc76dd7e5182f57131478e23d0dc59e24", size = 16613492, upload-time = "2026-03-09T07:57:54.91Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/86/1b6020db73be330c4b45d5c6ee4295d59cfeef0e3ea323959d053e5a6909/numpy-2.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d84f0f881cb2225c2dfd7f78a10a5645d487a496c6668d6cc39f0f114164f3d0", size = 17031789, upload-time = "2026-03-09T07:57:57.641Z" },
+ { url = "https://files.pythonhosted.org/packages/07/3a/3b90463bf41ebc21d1b7e06079f03070334374208c0f9a1f05e4ae8455e7/numpy-2.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d213c7e6e8d211888cc359bab7199670a00f5b82c0978b9d1c75baf1eddbeac0", size = 18339941, upload-time = "2026-03-09T07:58:00.577Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/74/6d736c4cd962259fd8bae9be27363eb4883a2f9069763747347544c2a487/numpy-2.4.3-cp314-cp314-win32.whl", hash = "sha256:52077feedeff7c76ed7c9f1a0428558e50825347b7545bbb8523da2cd55c547a", size = 6007503, upload-time = "2026-03-09T07:58:03.331Z" },
+ { url = "https://files.pythonhosted.org/packages/48/39/c56ef87af669364356bb011922ef0734fc49dad51964568634c72a009488/numpy-2.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:0448e7f9caefb34b4b7dd2b77f21e8906e5d6f0365ad525f9f4f530b13df2afc", size = 12444915, upload-time = "2026-03-09T07:58:06.353Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/1f/ab8528e38d295fd349310807496fabb7cf9fe2e1f70b97bc20a483ea9d4a/numpy-2.4.3-cp314-cp314-win_arm64.whl", hash = "sha256:b44fd60341c4d9783039598efadd03617fa28d041fc37d22b62d08f2027fa0e7", size = 10494875, upload-time = "2026-03-09T07:58:08.734Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/ef/b7c35e4d5ef141b836658ab21a66d1a573e15b335b1d111d31f26c8ef80f/numpy-2.4.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0a195f4216be9305a73c0e91c9b026a35f2161237cf1c6de9b681637772ea657", size = 14822225, upload-time = "2026-03-09T07:58:11.034Z" },
+ { url = "https://files.pythonhosted.org/packages/cd/8d/7730fa9278cf6648639946cc816e7cc89f0d891602584697923375f801ed/numpy-2.4.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:cd32fbacb9fd1bf041bf8e89e4576b6f00b895f06d00914820ae06a616bdfef7", size = 5328769, upload-time = "2026-03-09T07:58:13.67Z" },
+ { url = "https://files.pythonhosted.org/packages/47/01/d2a137317c958b074d338807c1b6a383406cdf8b8e53b075d804cc3d211d/numpy-2.4.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:2e03c05abaee1f672e9d67bc858f300b5ccba1c21397211e8d77d98350972093", size = 6649461, upload-time = "2026-03-09T07:58:15.912Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/34/812ce12bc0f00272a4b0ec0d713cd237cb390666eb6206323d1cc9cedbb2/numpy-2.4.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d1ce23cce91fcea443320a9d0ece9b9305d4368875bab09538f7a5b4131938a", size = 15725809, upload-time = "2026-03-09T07:58:17.787Z" },
+ { url = "https://files.pythonhosted.org/packages/25/c0/2aed473a4823e905e765fee3dc2cbf504bd3e68ccb1150fbdabd5c39f527/numpy-2.4.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c59020932feb24ed49ffd03704fbab89f22aa9c0d4b180ff45542fe8918f5611", size = 16655242, upload-time = "2026-03-09T07:58:20.476Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/c8/7e052b2fc87aa0e86de23f20e2c42bd261c624748aa8efd2c78f7bb8d8c6/numpy-2.4.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9684823a78a6cd6ad7511fc5e25b07947d1d5b5e2812c93fe99d7d4195130720", size = 17080660, upload-time = "2026-03-09T07:58:23.067Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/3d/0876746044db2adcb11549f214d104f2e1be00f07a67edbb4e2812094847/numpy-2.4.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0200b25c687033316fb39f0ff4e3e690e8957a2c3c8d22499891ec58c37a3eb5", size = 18380384, upload-time = "2026-03-09T07:58:25.839Z" },
+ { url = "https://files.pythonhosted.org/packages/07/12/8160bea39da3335737b10308df4f484235fd297f556745f13092aa039d3b/numpy-2.4.3-cp314-cp314t-win32.whl", hash = "sha256:5e10da9e93247e554bb1d22f8edc51847ddd7dde52d85ce31024c1b4312bfba0", size = 6154547, upload-time = "2026-03-09T07:58:28.289Z" },
+ { url = "https://files.pythonhosted.org/packages/42/f3/76534f61f80d74cc9cdf2e570d3d4eeb92c2280a27c39b0aaf471eda7b48/numpy-2.4.3-cp314-cp314t-win_amd64.whl", hash = "sha256:45f003dbdffb997a03da2d1d0cb41fbd24a87507fb41605c0420a3db5bd4667b", size = 12633645, upload-time = "2026-03-09T07:58:30.384Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/b6/7c0d4334c15983cec7f92a69e8ce9b1e6f31857e5ee3a413ac424e6bd63d/numpy-2.4.3-cp314-cp314t-win_arm64.whl", hash = "sha256:4d382735cecd7bcf090172489a525cd7d4087bc331f7df9f60ddc9a296cf208e", size = 10565454, upload-time = "2026-03-09T07:58:33.031Z" },
+ { url = "https://files.pythonhosted.org/packages/64/e4/4dab9fb43c83719c29241c535d9e07be73bea4bc0c6686c5816d8e1b6689/numpy-2.4.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c6b124bfcafb9e8d3ed09130dbee44848c20b3e758b6bbf006e641778927c028", size = 16834892, upload-time = "2026-03-09T07:58:35.334Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/29/f8b6d4af90fed3dfda84ebc0df06c9833d38880c79ce954e5b661758aa31/numpy-2.4.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:76dbb9d4e43c16cf9aa711fcd8de1e2eeb27539dcefb60a1d5e9f12fae1d1ed8", size = 14893070, upload-time = "2026-03-09T07:58:37.7Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/04/a19b3c91dbec0a49269407f15d5753673a09832daed40c45e8150e6fa558/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:29363fbfa6f8ee855d7569c96ce524845e3d726d6c19b29eceec7dd555dab152", size = 5399609, upload-time = "2026-03-09T07:58:39.853Z" },
+ { url = "https://files.pythonhosted.org/packages/79/34/4d73603f5420eab89ea8a67097b31364bf7c30f811d4dd84b1659c7476d9/numpy-2.4.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:bc71942c789ef415a37f0d4eab90341425a00d538cd0642445d30b41023d3395", size = 6714355, upload-time = "2026-03-09T07:58:42.365Z" },
+ { url = "https://files.pythonhosted.org/packages/58/ad/1100d7229bb248394939a12a8074d485b655e8ed44207d328fdd7fcebc7b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e58765ad74dcebd3ef0208a5078fba32dc8ec3578fe84a604432950cd043d79", size = 15800434, upload-time = "2026-03-09T07:58:44.837Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/fd/16d710c085d28ba4feaf29ac60c936c9d662e390344f94a6beaa2ac9899b/numpy-2.4.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e236dbda4e1d319d681afcbb136c0c4a8e0f1a5c58ceec2adebb547357fe857", size = 16729409, upload-time = "2026-03-09T07:58:47.972Z" },
+ { url = "https://files.pythonhosted.org/packages/57/a7/b35835e278c18b85206834b3aa3abe68e77a98769c59233d1f6300284781/numpy-2.4.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4b42639cdde6d24e732ff823a3fa5b701d8acad89c4142bc1d0bd6dc85200ba5", size = 12504685, upload-time = "2026-03-09T07:58:50.525Z" },
+]
+
+[[package]]
+name = "opentelemetry-api"
+version = "1.40.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "importlib-metadata" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz", hash = "sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f", size = 70851, upload-time = "2026-03-04T14:17:21.555Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl", hash = "sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9", size = 68676, upload-time = "2026-03-04T14:17:01.24Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
+]
+
+[[package]]
+name = "pandas"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+ { name = "python-dateutil" },
+ { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2e/0c/b28ed414f080ee0ad153f848586d61d1878f91689950f037f976ce15f6c8/pandas-3.0.1.tar.gz", hash = "sha256:4186a699674af418f655dbd420ed87f50d56b4cd6603784279d9eef6627823c8", size = 4641901, upload-time = "2026-02-17T22:20:16.434Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ff/07/c7087e003ceee9b9a82539b40414ec557aa795b584a1a346e89180853d79/pandas-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de09668c1bf3b925c07e5762291602f0d789eca1b3a781f99c1c78f6cac0e7ea", size = 10323380, upload-time = "2026-02-17T22:18:16.133Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/27/90683c7122febeefe84a56f2cde86a9f05f68d53885cebcc473298dfc33e/pandas-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24ba315ba3d6e5806063ac6eb717504e499ce30bd8c236d8693a5fd3f084c796", size = 9923455, upload-time = "2026-02-17T22:18:19.13Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/f1/ed17d927f9950643bc7631aa4c99ff0cc83a37864470bc419345b656a41f/pandas-3.0.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:406ce835c55bac912f2a0dcfaf27c06d73c6b04a5dde45f1fd3169ce31337389", size = 10753464, upload-time = "2026-02-17T22:18:21.134Z" },
+ { url = "https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:830994d7e1f31dd7e790045235605ab61cff6c94defc774547e8b7fdfbff3dc7", size = 11255234, upload-time = "2026-02-17T22:18:24.175Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/39/3653fe59af68606282b989c23d1a543ceba6e8099cbcc5f1d506a7bae2aa/pandas-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a64ce8b0f2de1d2efd2ae40b0abe7f8ae6b29fbfb3812098ed5a6f8e235ad9bf", size = 11767299, upload-time = "2026-02-17T22:18:26.824Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/31/1daf3c0c94a849c7a8dab8a69697b36d313b229918002ba3e409265c7888/pandas-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9832c2c69da24b602c32e0c7b1b508a03949c18ba08d4d9f1c1033426685b447", size = 12333292, upload-time = "2026-02-17T22:18:28.996Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:84f0904a69e7365f79a0c77d3cdfccbfb05bf87847e3a51a41e1426b0edb9c79", size = 9892176, upload-time = "2026-02-17T22:18:31.79Z" },
+ { url = "https://files.pythonhosted.org/packages/79/ab/9c776b14ac4b7b4140788eca18468ea39894bc7340a408f1d1e379856a6b/pandas-3.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:4a68773d5a778afb31d12e34f7dd4612ab90de8c6fb1d8ffe5d4a03b955082a1", size = 9151328, upload-time = "2026-02-17T22:18:35.721Z" },
+ { url = "https://files.pythonhosted.org/packages/37/51/b467209c08dae2c624873d7491ea47d2b47336e5403309d433ea79c38571/pandas-3.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:476f84f8c20c9f5bc47252b66b4bb25e1a9fc2fa98cead96744d8116cb85771d", size = 10344357, upload-time = "2026-02-17T22:18:38.262Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f1/e2567ffc8951ab371db2e40b2fe068e36b81d8cf3260f06ae508700e5504/pandas-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0ab749dfba921edf641d4036c4c21c0b3ea70fea478165cb98a998fb2a261955", size = 9884543, upload-time = "2026-02-17T22:18:41.476Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/39/327802e0b6d693182403c144edacbc27eb82907b57062f23ef5a4c4a5ea7/pandas-3.0.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8e36891080b87823aff3640c78649b91b8ff6eea3c0d70aeabd72ea43ab069b", size = 10396030, upload-time = "2026-02-17T22:18:43.822Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:532527a701281b9dd371e2f582ed9094f4c12dd9ffb82c0c54ee28d8ac9520c4", size = 10876435, upload-time = "2026-02-17T22:18:45.954Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/a6/2a75320849dd154a793f69c951db759aedb8d1dd3939eeacda9bdcfa1629/pandas-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:356e5c055ed9b0da1580d465657bc7d00635af4fd47f30afb23025352ba764d1", size = 11405133, upload-time = "2026-02-17T22:18:48.533Z" },
+ { url = "https://files.pythonhosted.org/packages/58/53/1d68fafb2e02d7881df66aa53be4cd748d25cbe311f3b3c85c93ea5d30ca/pandas-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9d810036895f9ad6345b8f2a338dd6998a74e8483847403582cab67745bff821", size = 11932065, upload-time = "2026-02-17T22:18:50.837Z" },
+ { url = "https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:536232a5fe26dd989bd633e7a0c450705fdc86a207fec7254a55e9a22950fe43", size = 9741627, upload-time = "2026-02-17T22:18:53.905Z" },
+ { url = "https://files.pythonhosted.org/packages/86/4f/caf9952948fb00d23795f09b893d11f1cacb384e666854d87249530f7cbe/pandas-3.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:0f463ebfd8de7f326d38037c7363c6dacb857c5881ab8961fb387804d6daf2f7", size = 9052483, upload-time = "2026-02-17T22:18:57.31Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/48/aad6ec4f8d007534c091e9a7172b3ec1b1ee6d99a9cbb936b5eab6c6cf58/pandas-3.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5272627187b5d9c20e55d27caf5f2cd23e286aba25cadf73c8590e432e2b7262", size = 10317509, upload-time = "2026-02-17T22:18:59.498Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/14/5990826f779f79148ae9d3a2c39593dc04d61d5d90541e71b5749f35af95/pandas-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:661e0f665932af88c7877f31da0dc743fe9c8f2524bdffe23d24fdcb67ef9d56", size = 9860561, upload-time = "2026-02-17T22:19:02.265Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/80/f01ff54664b6d70fed71475543d108a9b7c888e923ad210795bef04ffb7d/pandas-3.0.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75e6e292ff898679e47a2199172593d9f6107fd2dd3617c22c2946e97d5df46e", size = 10365506, upload-time = "2026-02-17T22:19:05.017Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ff8cf1d2896e34343197685f432450ec99a85ba8d90cce2030c5eee2ef98791", size = 10873196, upload-time = "2026-02-17T22:19:07.204Z" },
+ { url = "https://files.pythonhosted.org/packages/48/a9/9301c83d0b47c23ac5deab91c6b39fd98d5b5db4d93b25df8d381451828f/pandas-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eca8b4510f6763f3d37359c2105df03a7a221a508f30e396a51d0713d462e68a", size = 11370859, upload-time = "2026-02-17T22:19:09.436Z" },
+ { url = "https://files.pythonhosted.org/packages/59/fe/0c1fc5bd2d29c7db2ab372330063ad555fb83e08422829c785f5ec2176ca/pandas-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06aff2ad6f0b94a17822cf8b83bbb563b090ed82ff4fe7712db2ce57cd50d9b8", size = 11924584, upload-time = "2026-02-17T22:19:11.562Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9fea306c783e28884c29057a1d9baa11a349bbf99538ec1da44c8476563d1b25", size = 9742769, upload-time = "2026-02-17T22:19:13.926Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/cb/810a22a6af9a4e97c8ab1c946b47f3489c5bca5adc483ce0ffc84c9cc768/pandas-3.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:a8d37a43c52917427e897cb2e429f67a449327394396a81034a4449b99afda59", size = 9043855, upload-time = "2026-02-17T22:19:16.09Z" },
+ { url = "https://files.pythonhosted.org/packages/92/fa/423c89086cca1f039cf1253c3ff5b90f157b5b3757314aa635f6bf3e30aa/pandas-3.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d54855f04f8246ed7b6fc96b05d4871591143c46c0b6f4af874764ed0d2d6f06", size = 10752673, upload-time = "2026-02-17T22:19:18.304Z" },
+ { url = "https://files.pythonhosted.org/packages/22/23/b5a08ec1f40020397f0faba72f1e2c11f7596a6169c7b3e800abff0e433f/pandas-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e1b677accee34a09e0dc2ce5624e4a58a1870ffe56fc021e9caf7f23cd7668f", size = 10404967, upload-time = "2026-02-17T22:19:20.726Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/81/94841f1bb4afdc2b52a99daa895ac2c61600bb72e26525ecc9543d453ebc/pandas-3.0.1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9cabbdcd03f1b6cd254d6dda8ae09b0252524be1592594c00b7895916cb1324", size = 10320575, upload-time = "2026-02-17T22:19:24.919Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/8b/2ae37d66a5342a83adadfd0cb0b4bf9c3c7925424dd5f40d15d6cfaa35ee/pandas-3.0.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ae2ab1f166668b41e770650101e7090824fd34d17915dd9cd479f5c5e0065e9", size = 10710921, upload-time = "2026-02-17T22:19:27.181Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/61/772b2e2757855e232b7ccf7cb8079a5711becb3a97f291c953def15a833f/pandas-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6bf0603c2e30e2cafac32807b06435f28741135cb8697eae8b28c7d492fc7d76", size = 11334191, upload-time = "2026-02-17T22:19:29.411Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/08/b16c6df3ef555d8495d1d265a7963b65be166785d28f06a350913a4fac78/pandas-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c426422973973cae1f4a23e51d4ae85974f44871b24844e4f7de752dd877098", size = 11782256, upload-time = "2026-02-17T22:19:32.34Z" },
+ { url = "https://files.pythonhosted.org/packages/55/80/178af0594890dee17e239fca96d3d8670ba0f5ff59b7d0439850924a9c09/pandas-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b03f91ae8c10a85c1613102c7bef5229b5379f343030a3ccefeca8a33414cf35", size = 10485047, upload-time = "2026-02-17T22:19:34.605Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/8b/4bb774a998b97e6c2fd62a9e6cfdaae133b636fd1c468f92afb4ae9a447a/pandas-3.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:99d0f92ed92d3083d140bf6b97774f9f13863924cf3f52a70711f4e7588f9d0a", size = 10322465, upload-time = "2026-02-17T22:19:36.803Z" },
+ { url = "https://files.pythonhosted.org/packages/72/3a/5b39b51c64159f470f1ca3b1c2a87da290657ca022f7cd11442606f607d1/pandas-3.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b66857e983208654294bb6477b8a63dee26b37bdd0eb34d010556e91261784f", size = 9910632, upload-time = "2026-02-17T22:19:39.001Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/f7/b449ffb3f68c11da12fc06fbf6d2fa3a41c41e17d0284d23a79e1c13a7e4/pandas-3.0.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56cf59638bf24dc9bdf2154c81e248b3289f9a09a6d04e63608c159022352749", size = 10440535, upload-time = "2026-02-17T22:19:41.157Z" },
+ { url = "https://files.pythonhosted.org/packages/55/77/6ea82043db22cb0f2bbfe7198da3544000ddaadb12d26be36e19b03a2dc5/pandas-3.0.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1a9f55e0f46951874b863d1f3906dcb57df2d9be5c5847ba4dfb55b2c815249", size = 10893940, upload-time = "2026-02-17T22:19:43.493Z" },
+ { url = "https://files.pythonhosted.org/packages/03/30/f1b502a72468c89412c1b882a08f6eed8a4ee9dc033f35f65d0663df6081/pandas-3.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1849f0bba9c8a2fb0f691d492b834cc8dadf617e29015c66e989448d58d011ee", size = 11442711, upload-time = "2026-02-17T22:19:46.074Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/f0/ebb6ddd8fc049e98cabac5c2924d14d1dda26a20adb70d41ea2e428d3ec4/pandas-3.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3d288439e11b5325b02ae6e9cc83e6805a62c40c5a6220bea9beb899c073b1c", size = 11963918, upload-time = "2026-02-17T22:19:48.838Z" },
+ { url = "https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:93325b0fe372d192965f4cca88d97667f49557398bbf94abdda3bf1b591dbe66", size = 9862099, upload-time = "2026-02-17T22:19:51.081Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/b7/6af9aac41ef2456b768ef0ae60acf8abcebb450a52043d030a65b4b7c9bd/pandas-3.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:97ca08674e3287c7148f4858b01136f8bdfe7202ad25ad04fec602dd1d29d132", size = 9185333, upload-time = "2026-02-17T22:19:53.266Z" },
+ { url = "https://files.pythonhosted.org/packages/66/fc/848bb6710bc6061cb0c5badd65b92ff75c81302e0e31e496d00029fe4953/pandas-3.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:58eeb1b2e0fb322befcf2bbc9ba0af41e616abadb3d3414a6bc7167f6cbfce32", size = 10772664, upload-time = "2026-02-17T22:19:55.806Z" },
+ { url = "https://files.pythonhosted.org/packages/69/5c/866a9bbd0f79263b4b0db6ec1a341be13a1473323f05c122388e0f15b21d/pandas-3.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cd9af1276b5ca9e298bd79a26bda32fa9cc87ed095b2a9a60978d2ca058eaf87", size = 10421286, upload-time = "2026-02-17T22:19:58.091Z" },
+ { url = "https://files.pythonhosted.org/packages/51/a4/2058fb84fb1cfbfb2d4a6d485e1940bb4ad5716e539d779852494479c580/pandas-3.0.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94f87a04984d6b63788327cd9f79dda62b7f9043909d2440ceccf709249ca988", size = 10342050, upload-time = "2026-02-17T22:20:01.376Z" },
+ { url = "https://files.pythonhosted.org/packages/22/1b/674e89996cc4be74db3c4eb09240c4bb549865c9c3f5d9b086ff8fcfbf00/pandas-3.0.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85fe4c4df62e1e20f9db6ebfb88c844b092c22cd5324bdcf94bfa2fc1b391221", size = 10740055, upload-time = "2026-02-17T22:20:04.328Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/f8/e954b750764298c22fa4614376531fe63c521ef517e7059a51f062b87dca/pandas-3.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:331ca75a2f8672c365ae25c0b29e46f5ac0c6551fdace8eec4cd65e4fac271ff", size = 11357632, upload-time = "2026-02-17T22:20:06.647Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/02/c6e04b694ffd68568297abd03588b6d30295265176a5c01b7459d3bc35a3/pandas-3.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:15860b1fdb1973fffade772fdb931ccf9b2f400a3f5665aef94a00445d7d8dd5", size = 11810974, upload-time = "2026-02-17T22:20:08.946Z" },
+ { url = "https://files.pythonhosted.org/packages/89/41/d7dfb63d2407f12055215070c42fc6ac41b66e90a2946cdc5e759058398b/pandas-3.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:44f1364411d5670efa692b146c748f4ed013df91ee91e9bec5677fb1fd58b937", size = 10884622, upload-time = "2026-02-17T22:20:11.711Z" },
+ { url = "https://files.pythonhosted.org/packages/68/b0/34937815889fa982613775e4b97fddd13250f11012d769949c5465af2150/pandas-3.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:108dd1790337a494aa80e38def654ca3f0968cf4f362c85f44c15e471667102d", size = 9452085, upload-time = "2026-02-17T22:20:14.331Z" },
+]
+
+[[package]]
+name = "pandocfilters"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" },
+]
+
+[[package]]
+name = "parso"
+version = "0.8.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/81/76/a1e769043c0c0c9fe391b702539d594731a4362334cdf4dc25d0c09761e7/parso-0.8.6.tar.gz", hash = "sha256:2b9a0332696df97d454fa67b81618fd69c35a7b90327cbe6ba5c92d2c68a7bfd", size = 401621, upload-time = "2026-02-09T15:45:24.425Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl", hash = "sha256:2c549f800b70a5c4952197248825584cb00f033b29c692671d3bf08bf380baff", size = 106894, upload-time = "2026-02-09T15:45:21.391Z" },
+]
+
+[[package]]
+name = "partd"
+version = "1.4.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "locket" },
+ { name = "toolz" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b2/3a/3f06f34820a31257ddcabdfafc2672c5816be79c7e353b02c1f318daa7d4/partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c", size = 21029, upload-time = "2024-05-06T19:51:41.945Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/e7/40fb618334dcdf7c5a316c0e7343c5cd82d3d866edc100d98e29bc945ecd/partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f", size = 18905, upload-time = "2024-05-06T19:51:39.271Z" },
+]
+
+[[package]]
+name = "pathspec"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/2e/17/9c3094b822982b9f1ea666d8580ce59000f61f87c1663556fb72031ad9ec/pathspec-1.1.0.tar.gz", hash = "sha256:f5d7c555da02fd8dde3e4a2354b6aba817a89112fa8f333f7917a2a4834dd080", size = 133918, upload-time = "2026-04-23T01:46:22.298Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fa/c9/8eed0486f074e9f1ca7f8ce5ad663e65f12fdab344028d658fa1b03d35e0/pathspec-1.1.0-py3-none-any.whl", hash = "sha256:574b128f7456bd899045ccd142dd446af7e6cfd0072d63ad73fbc55fbb4aaa42", size = 56264, upload-time = "2026-04-23T01:46:20.606Z" },
+]
+
+[[package]]
+name = "pexpect"
+version = "4.9.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "ptyprocess", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" },
+]
+
+[[package]]
+name = "pillow"
+version = "12.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" },
+ { url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" },
+ { url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" },
+ { url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" },
+ { url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" },
+ { url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" },
+ { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
+ { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
+ { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
+ { url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
+ { url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
+ { url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
+ { url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
+ { url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
+ { url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
+ { url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
+ { url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
+ { url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
+ { url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
+ { url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
+ { url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
+ { url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
+ { url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
+ { url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
+ { url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
+ { url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
+ { url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
+ { url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
+ { url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
+ { url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
+ { url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
+ { url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
+ { url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" },
+ { url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" },
+ { url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" },
+ { url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" },
+ { url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" },
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.9.4"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" },
+]
+
+[[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 = "prompt-toolkit"
+version = "3.0.52"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "wcwidth" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" },
+]
+
+[[package]]
+name = "psutil"
+version = "7.2.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" },
+ { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" },
+ { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" },
+ { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" },
+ { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" },
+ { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" },
+ { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" },
+ { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" },
+]
+
+[[package]]
+name = "ptyprocess"
+version = "0.7.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" },
+]
+
+[[package]]
+name = "pure-eval"
+version = "0.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" },
+]
+
+[[package]]
+name = "pyarrow"
+version = "23.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/88/22/134986a4cc224d593c1afde5494d18ff629393d74cc2eddb176669f234a4/pyarrow-23.0.1.tar.gz", hash = "sha256:b8c5873e33440b2bc2f4a79d2b47017a89c5a24116c055625e6f2ee50523f019", size = 1167336, upload-time = "2026-02-16T10:14:12.39Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b0/41/8e6b6ef7e225d4ceead8459427a52afdc23379768f54dd3566014d7618c1/pyarrow-23.0.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6f0147ee9e0386f519c952cc670eb4a8b05caa594eeffe01af0e25f699e4e9bb", size = 34302230, upload-time = "2026-02-16T10:09:03.859Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/4a/1472c00392f521fea03ae93408bf445cc7bfa1ab81683faf9bc188e36629/pyarrow-23.0.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:0ae6e17c828455b6265d590100c295193f93cc5675eb0af59e49dbd00d2de350", size = 35850050, upload-time = "2026-02-16T10:09:11.877Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/b2/bd1f2f05ded56af7f54d702c8364c9c43cd6abb91b0e9933f3d77b4f4132/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:fed7020203e9ef273360b9e45be52a2a47d3103caf156a30ace5247ffb51bdbd", size = 44491918, upload-time = "2026-02-16T10:09:18.144Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/62/96459ef5b67957eac38a90f541d1c28833d1b367f014a482cb63f3b7cd2d/pyarrow-23.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:26d50dee49d741ac0e82185033488d28d35be4d763ae6f321f97d1140eb7a0e9", size = 47562811, upload-time = "2026-02-16T10:09:25.792Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/94/1170e235add1f5f45a954e26cd0e906e7e74e23392dcb560de471f7366ec/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3c30143b17161310f151f4a2bcfe41b5ff744238c1039338779424e38579d701", size = 48183766, upload-time = "2026-02-16T10:09:34.645Z" },
+ { url = "https://files.pythonhosted.org/packages/0e/2d/39a42af4570377b99774cdb47f63ee6c7da7616bd55b3d5001aa18edfe4f/pyarrow-23.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db2190fa79c80a23fdd29fef4b8992893f024ae7c17d2f5f4db7171fa30c2c78", size = 50607669, upload-time = "2026-02-16T10:09:44.153Z" },
+ { url = "https://files.pythonhosted.org/packages/00/ca/db94101c187f3df742133ac837e93b1f269ebdac49427f8310ee40b6a58f/pyarrow-23.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:f00f993a8179e0e1c9713bcc0baf6d6c01326a406a9c23495ec1ba9c9ebf2919", size = 27527698, upload-time = "2026-02-16T10:09:50.263Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/4b/4166bb5abbfe6f750fc60ad337c43ecf61340fa52ab386da6e8dbf9e63c4/pyarrow-23.0.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:f4b0dbfa124c0bb161f8b5ebb40f1a680b70279aa0c9901d44a2b5a20806039f", size = 34214575, upload-time = "2026-02-16T10:09:56.225Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/da/3f941e3734ac8088ea588b53e860baeddac8323ea40ce22e3d0baa865cc9/pyarrow-23.0.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:7707d2b6673f7de054e2e83d59f9e805939038eebe1763fe811ee8fa5c0cd1a7", size = 35832540, upload-time = "2026-02-16T10:10:03.428Z" },
+ { url = "https://files.pythonhosted.org/packages/88/7c/3d841c366620e906d54430817531b877ba646310296df42ef697308c2705/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:86ff03fb9f1a320266e0de855dee4b17da6794c595d207f89bba40d16b5c78b9", size = 44470940, upload-time = "2026-02-16T10:10:10.704Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/a5/da83046273d990f256cb79796a190bbf7ec999269705ddc609403f8c6b06/pyarrow-23.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:813d99f31275919c383aab17f0f455a04f5a429c261cc411b1e9a8f5e4aaaa05", size = 47586063, upload-time = "2026-02-16T10:10:17.95Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/3c/b7d2ebcff47a514f47f9da1e74b7949138c58cfeb108cdd4ee62f43f0cf3/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bf5842f960cddd2ef757d486041d57c96483efc295a8c4a0e20e704cbbf39c67", size = 48173045, upload-time = "2026-02-16T10:10:25.363Z" },
+ { url = "https://files.pythonhosted.org/packages/43/b2/b40961262213beaba6acfc88698eb773dfce32ecdf34d19291db94c2bd73/pyarrow-23.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564baf97c858ecc03ec01a41062e8f4698abc3e6e2acd79c01c2e97880a19730", size = 50621741, upload-time = "2026-02-16T10:10:33.477Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/70/1fdda42d65b28b078e93d75d371b2185a61da89dda4def8ba6ba41ebdeb4/pyarrow-23.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:07deae7783782ac7250989a7b2ecde9b3c343a643f82e8a4df03d93b633006f0", size = 27620678, upload-time = "2026-02-16T10:10:39.31Z" },
+ { url = "https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8", size = 34210066, upload-time = "2026-02-16T10:10:45.487Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/4f/679fa7e84dadbaca7a65f7cdba8d6c83febbd93ca12fa4adf40ba3b6362b/pyarrow-23.0.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:8ff51b1addc469b9444b7c6f3548e19dc931b172ab234e995a60aea9f6e6025f", size = 35825526, upload-time = "2026-02-16T10:10:52.266Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677", size = 44473279, upload-time = "2026-02-16T10:11:01.557Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2", size = 47585798, upload-time = "2026-02-16T10:11:09.401Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/20/476943001c54ef078dbf9542280e22741219a184a0632862bca4feccd666/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fc13fc6c403d1337acab46a2c4346ca6c9dec5780c3c697cf8abfd5e19b6b37", size = 48179446, upload-time = "2026-02-16T10:11:17.781Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/b6/5dd0c47b335fcd8edba9bfab78ad961bd0fd55ebe53468cc393f45e0be60/pyarrow-23.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5c16ed4f53247fa3ffb12a14d236de4213a4415d127fe9cebed33d51671113e2", size = 50623972, upload-time = "2026-02-16T10:11:26.185Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/09/a532297c9591a727d67760e2e756b83905dd89adb365a7f6e9c72578bcc1/pyarrow-23.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:cecfb12ef629cf6be0b1887f9f86463b0dd3dc3195ae6224e74006be4736035a", size = 27540749, upload-time = "2026-02-16T10:12:23.297Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/8e/38749c4b1303e6ae76b3c80618f84861ae0c55dd3c2273842ea6f8258233/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:29f7f7419a0e30264ea261fdc0e5fe63ce5a6095003db2945d7cd78df391a7e1", size = 34471544, upload-time = "2026-02-16T10:11:32.535Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/73/f237b2bc8c669212f842bcfd842b04fc8d936bfc9d471630569132dc920d/pyarrow-23.0.1-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:33d648dc25b51fd8055c19e4261e813dfc4d2427f068bcecc8b53d01b81b0500", size = 35949911, upload-time = "2026-02-16T10:11:39.813Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/86/b912195eee0903b5611bf596833def7d146ab2d301afeb4b722c57ffc966/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd395abf8f91c673dd3589cadc8cc1ee4e8674fa61b2e923c8dd215d9c7d1f41", size = 44520337, upload-time = "2026-02-16T10:11:47.764Z" },
+ { url = "https://files.pythonhosted.org/packages/69/c2/f2a717fb824f62d0be952ea724b4f6f9372a17eed6f704b5c9526f12f2f1/pyarrow-23.0.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:00be9576d970c31defb5c32eb72ef585bf600ef6d0a82d5eccaae96639cf9d07", size = 47548944, upload-time = "2026-02-16T10:11:56.607Z" },
+ { url = "https://files.pythonhosted.org/packages/84/a7/90007d476b9f0dc308e3bc57b832d004f848fd6c0da601375d20d92d1519/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c2139549494445609f35a5cda4eb94e2c9e4d704ce60a095b342f82460c73a83", size = 48236269, upload-time = "2026-02-16T10:12:04.47Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/3f/b16fab3e77709856eb6ac328ce35f57a6d4a18462c7ca5186ef31b45e0e0/pyarrow-23.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7044b442f184d84e2351e5084600f0d7343d6117aabcbc1ac78eb1ae11eb4125", size = 50604794, upload-time = "2026-02-16T10:12:11.797Z" },
+ { url = "https://files.pythonhosted.org/packages/e9/a1/22df0620a9fac31d68397a75465c344e83c3dfe521f7612aea33e27ab6c0/pyarrow-23.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a35581e856a2fafa12f3f54fce4331862b1cfb0bef5758347a858a4aa9d6bae8", size = 27660642, upload-time = "2026-02-16T10:12:17.746Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/1b/6da9a89583ce7b23ac611f183ae4843cd3a6cf54f079549b0e8c14031e73/pyarrow-23.0.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:5df1161da23636a70838099d4aaa65142777185cc0cdba4037a18cee7d8db9ca", size = 34238755, upload-time = "2026-02-16T10:12:32.819Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/b5/d58a241fbe324dbaeb8df07be6af8752c846192d78d2272e551098f74e88/pyarrow-23.0.1-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:fa8e51cb04b9f8c9c5ace6bab63af9a1f88d35c0d6cbf53e8c17c098552285e1", size = 35847826, upload-time = "2026-02-16T10:12:38.949Z" },
+ { url = "https://files.pythonhosted.org/packages/54/a5/8cbc83f04aba433ca7b331b38f39e000efd9f0c7ce47128670e737542996/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:0b95a3994f015be13c63148fef8832e8a23938128c185ee951c98908a696e0eb", size = 44536859, upload-time = "2026-02-16T10:12:45.467Z" },
+ { url = "https://files.pythonhosted.org/packages/36/2e/c0f017c405fcdc252dbccafbe05e36b0d0eb1ea9a958f081e01c6972927f/pyarrow-23.0.1-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4982d71350b1a6e5cfe1af742c53dfb759b11ce14141870d05d9e540d13bc5d1", size = 47614443, upload-time = "2026-02-16T10:12:55.525Z" },
+ { url = "https://files.pythonhosted.org/packages/af/6b/2314a78057912f5627afa13ba43809d9d653e6630859618b0fd81a4e0759/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c250248f1fe266db627921c89b47b7c06fee0489ad95b04d50353537d74d6886", size = 48232991, upload-time = "2026-02-16T10:13:04.729Z" },
+ { url = "https://files.pythonhosted.org/packages/40/f2/1bcb1d3be3460832ef3370d621142216e15a2c7c62602a4ea19ec240dd64/pyarrow-23.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f4763b83c11c16e5f4c15601ba6dfa849e20723b46aa2617cb4bffe8768479f", size = 50645077, upload-time = "2026-02-16T10:13:14.147Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/3f/b1da7b61cd66566a4d4c8383d376c606d1c34a906c3f1cb35c479f59d1aa/pyarrow-23.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:3a4c85ef66c134161987c17b147d6bffdca4566f9a4c1d81a0a01cdf08414ea5", size = 28234271, upload-time = "2026-02-16T10:14:09.397Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/78/07f67434e910a0f7323269be7bfbf58699bd0c1d080b18a1ab49ba943fe8/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:17cd28e906c18af486a499422740298c52d7c6795344ea5002a7720b4eadf16d", size = 34488692, upload-time = "2026-02-16T10:13:21.541Z" },
+ { url = "https://files.pythonhosted.org/packages/50/76/34cf7ae93ece1f740a04910d9f7e80ba166b9b4ab9596a953e9e62b90fe1/pyarrow-23.0.1-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:76e823d0e86b4fb5e1cf4a58d293036e678b5a4b03539be933d3b31f9406859f", size = 35964383, upload-time = "2026-02-16T10:13:28.63Z" },
+ { url = "https://files.pythonhosted.org/packages/46/90/459b827238936d4244214be7c684e1b366a63f8c78c380807ae25ed92199/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a62e1899e3078bf65943078b3ad2a6ddcacf2373bc06379aac61b1e548a75814", size = 44538119, upload-time = "2026-02-16T10:13:35.506Z" },
+ { url = "https://files.pythonhosted.org/packages/28/a1/93a71ae5881e99d1f9de1d4554a87be37da11cd6b152239fb5bd924fdc64/pyarrow-23.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:df088e8f640c9fae3b1f495b3c64755c4e719091caf250f3a74d095ddf3c836d", size = 47571199, upload-time = "2026-02-16T10:13:42.504Z" },
+ { url = "https://files.pythonhosted.org/packages/88/a3/d2c462d4ef313521eaf2eff04d204ac60775263f1fb08c374b543f79f610/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:46718a220d64677c93bc243af1d44b55998255427588e400677d7192671845c7", size = 48259435, upload-time = "2026-02-16T10:13:49.226Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/f1/11a544b8c3d38a759eb3fbb022039117fd633e9a7b19e4841cc3da091915/pyarrow-23.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a09f3876e87f48bc2f13583ab551f0379e5dfb83210391e68ace404181a20690", size = 50629149, upload-time = "2026-02-16T10:13:57.238Z" },
+ { url = "https://files.pythonhosted.org/packages/50/f2/c0e76a0b451ffdf0cf788932e182758eb7558953f4f27f1aff8e2518b653/pyarrow-23.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:527e8d899f14bd15b740cd5a54ad56b7f98044955373a17179d5956ddb93d9ce", size = 28365807, upload-time = "2026-02-16T10:14:03.892Z" },
+]
+
+[[package]]
+name = "pycparser"
+version = "3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
+]
+
+[[package]]
+name = "pydata-sphinx-theme"
+version = "0.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "accessible-pygments" },
+ { name = "babel" },
+ { name = "beautifulsoup4" },
+ { name = "docutils" },
+ { name = "pygments" },
+ { name = "sphinx", version = "9.0.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" },
+ { name = "sphinx", version = "9.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/ce/f7/c74c7100a7f4c0f77b5dcacb7dfdb8fee774fb70e487dd97acba2b930774/pydata_sphinx_theme-0.17.1.tar.gz", hash = "sha256:2cfc1d926c753c77039b7ee53f0ccebcbee5e81f0db61432b01cbb10ad7fd0af", size = 4991415, upload-time = "2026-04-21T13:00:34.263Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e2/bc/2cb8c78300ce1ace4eeac3b3522218cea2c2053bfa6b4e32cc972a477f9a/pydata_sphinx_theme-0.17.1-py3-none-any.whl", hash = "sha256:320b022d7808bdf5920d9a28e573f27aace9b23e1af6ca103eecc752411df492", size = 6823346, upload-time = "2026-04-21T13:00:31.978Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
+]
+
+[[package]]
+name = "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 = "pyqt5"
+version = "5.15.11"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pyqt5-qt5" },
+ { name = "pyqt5-sip" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0e/07/c9ed0bd428df6f87183fca565a79fee19fa7c88c7f00a7f011ab4379e77a/PyQt5-5.15.11.tar.gz", hash = "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52", size = 3216775, upload-time = "2024-07-19T08:39:57.756Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/11/64/42ec1b0bd72d87f87bde6ceb6869f444d91a2d601f2e67cd05febc0346a1/PyQt5-5.15.11-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2", size = 6579776, upload-time = "2024-07-19T08:39:19.775Z" },
+ { url = "https://files.pythonhosted.org/packages/49/f5/3fb696f4683ea45d68b7e77302eff173493ac81e43d63adb60fa760b9f91/PyQt5-5.15.11-cp38-abi3-macosx_11_0_x86_64.whl", hash = "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6", size = 7016415, upload-time = "2024-07-19T08:39:32.977Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/8c/4065950f9d013c4b2e588fe33cf04e564c2322842d84dbcbce5ba1dc28b0/PyQt5-5.15.11-cp38-abi3-manylinux_2_17_x86_64.whl", hash = "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1", size = 8188103, upload-time = "2024-07-19T08:39:40.561Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/f0/ae5a5b4f9b826b29ea4be841b2f2d951bcf5ae1d802f3732b145b57c5355/PyQt5-5.15.11-cp38-abi3-win32.whl", hash = "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd", size = 5433308, upload-time = "2024-07-19T08:39:46.932Z" },
+ { url = "https://files.pythonhosted.org/packages/56/d5/68eb9f3d19ce65df01b6c7b7a577ad3bbc9ab3a5dd3491a4756e71838ec9/PyQt5-5.15.11-cp38-abi3-win_amd64.whl", hash = "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517", size = 6865864, upload-time = "2024-07-19T08:39:53.572Z" },
+]
+
+[[package]]
+name = "pyqt5-qt5"
+version = "5.15.18"
+source = { registry = "https://pypi.org/simple" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/46/90/bf01ac2132400997a3474051dd680a583381ebf98b2f5d64d4e54138dc42/pyqt5_qt5-5.15.18-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:8bb997eb903afa9da3221a0c9e6eaa00413bbeb4394d5706118ad05375684767", size = 39715743, upload-time = "2025-11-09T12:56:42.936Z" },
+ { url = "https://files.pythonhosted.org/packages/24/8e/76366484d9f9dbe28e3bdfc688183433a7b82e314216e9b14c89e5fab690/pyqt5_qt5-5.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c656af9c1e6aaa7f59bf3d8995f2fa09adbf6762b470ed284c31dca80d686a26", size = 36798484, upload-time = "2025-11-09T12:56:59.998Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/46/ffe177f99f897a59dc237a20059020427bd2d3853d713992b8081933ddfe/pyqt5_qt5-5.15.18-py3-none-manylinux2014_x86_64.whl", hash = "sha256:bf2457e6371969736b4f660a0c153258fa03dbc6a181348218e6f05421682af7", size = 60864590, upload-time = "2025-11-09T12:57:26.724Z" },
+]
+
+[[package]]
+name = "pyqt5-sip"
+version = "12.18.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f3/31/5ef342de9faee0f3801088946ae103db9b9eaeba3d6a64fefd5ce74df244/pyqt5_sip-12.18.0.tar.gz", hash = "sha256:71c37db75a0664325de149f43e2a712ec5fa1f90429a21dafbca005cb6767f94", size = 104143, upload-time = "2026-01-13T15:53:19.576Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9a/59/3dd29bcfde479ac241f618235bf7d76e65e47afdcd91743554d490ae0d19/pyqt5_sip-12.18.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13c32e9025d0ab5fe960ef64dcb4c6f7ec26156ddce2cf2f195600aa26e8f9fe", size = 122724, upload-time = "2026-01-13T15:52:49.875Z" },
+ { url = "https://files.pythonhosted.org/packages/87/c4/ac4deee3249d3ceb703103acbbf76d89f3782fa7ad2ca5a15fcb5bcf5c73/pyqt5_sip-12.18.0-cp311-cp311-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd855149c724634eb1f92d10e04e1be0751068a8521d5f9f06e5c2dbe32fd89f", size = 327560, upload-time = "2026-01-13T15:52:53.584Z" },
+ { url = "https://files.pythonhosted.org/packages/79/53/64373ba9311288b0ea6b5ab375d9c9743a41ac93df7137655498c95a08e5/pyqt5_sip-12.18.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:521729e4ce44db555005f4e6987c4a5164d45ea03f5a7d08519813d4776acd15", size = 277067, upload-time = "2026-01-13T15:52:51.22Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/dd/4026bba8355ceaba222db0369fe4a480d55aa61d3f14dbe1458886ccc032/pyqt5_sip-12.18.0-cp311-cp311-win32.whl", hash = "sha256:c738949863d88b78f86f28e13151989ca3b1a302934811af41856dc8a27838bd", size = 49323, upload-time = "2026-01-13T15:52:55.859Z" },
+ { url = "https://files.pythonhosted.org/packages/41/35/39e549ca7f7c9fbb025912a5db7f55f3b9c22d7f9718f41c2e7a17c806e9/pyqt5_sip-12.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac0be1a8ce145ed78c7e8d45243749405bee7e3a87e9810b0542bca010c50bd7", size = 58795, upload-time = "2026-01-13T15:52:54.995Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/61/6d78d702016ac23d2b97634a3b6a831c3f7735f0552a1c8b058db96005d1/pyqt5_sip-12.18.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b29e4cda24748e59e5bd1bdad4812091a86b4b5b08c38b7f781eb55a5166f2b7", size = 124614, upload-time = "2026-01-13T15:52:57.59Z" },
+ { url = "https://files.pythonhosted.org/packages/19/bf/8f3efa10ddd3e76c1253865340ab7c2960ef96681d732b1f666c77430612/pyqt5_sip-12.18.0-cp312-cp312-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:163c2bba5e637c2222ec17d82a2c5aa158184a191923eb7d137cf4cfa0399529", size = 339412, upload-time = "2026-01-13T15:53:00.563Z" },
+ { url = "https://files.pythonhosted.org/packages/72/48/f1bcf6729d01bae6729cd790b22fd579dbe34014e8be031e6f10c5b9b2aa/pyqt5_sip-12.18.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ead5e0a64ad852ac60797989d8444a6a5bd834768536b04a07b40b2937d922f6", size = 282376, upload-time = "2026-01-13T15:52:59.172Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/b7/d84c764ac9f1366be561255ec9bd88ee224fefdbdb349aee250f3003f0ca/pyqt5_sip-12.18.0-cp312-cp312-win32.whl", hash = "sha256:993fe3ed9a62a92e770f32d5344e3df56c2cacf1471f01b7feaf04818a2df1c4", size = 49523, upload-time = "2026-01-13T15:53:03.068Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/e7/ef87178d5afa5f63be38556dc0df8af89f9bf74f2555f4dab6824c0fd150/pyqt5_sip-12.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:9b689e02e400abd1ce0a30cd6eae8eceabcf1bbba0395cb5c86e64ba74351d68", size = 58001, upload-time = "2026-01-13T15:53:02.15Z" },
+ { url = "https://files.pythonhosted.org/packages/79/67/8d43d0fea10ff48ddecc8534aead8b855dc80df80653b8b1bf9e1f993063/pyqt5_sip-12.18.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9254e5dd7676b76503ba20edcc919e7ac4a97b6c70a6fb2f9dba9e13b4c60509", size = 124605, upload-time = "2026-01-13T15:53:04.991Z" },
+ { url = "https://files.pythonhosted.org/packages/48/2a/b08bc8efeb49c50c6cdac11417dc2c8eaefcac2f0a6382eae7b26dc0f232/pyqt5_sip-12.18.0-cp313-cp313-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c969631ada7293a81e1012b2264a62d69a91995b517586489dfe24421b87b9af", size = 339918, upload-time = "2026-01-13T15:53:08.502Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/99/24f82437b2f073cf39296b7c731b6a8bc0f5207911fdd93841a0ea9abe42/pyqt5_sip-12.18.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d84ac384a63285132e67762c87681191c25e28a1df7560287ec3889d9eb223b5", size = 282088, upload-time = "2026-01-13T15:53:06.632Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/27/20d3924943df34361fae9c6a0489ae89d0b07571693245c61678d185e4a4/pyqt5_sip-12.18.0-cp313-cp313-win32.whl", hash = "sha256:95bba4670ecf5cba73958b85aa2087c17838a402ed251c38e68060c7665c998b", size = 49501, upload-time = "2026-01-13T15:53:11.159Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/36/e251623c12968730730512a9e5150430e36246afbe64894610190b896f61/pyqt5_sip-12.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:aac4adc37df2f2ac1dc259409be1900f07332d140a12c9db7c84112cef64ff59", size = 58076, upload-time = "2026-01-13T15:53:09.928Z" },
+ { url = "https://files.pythonhosted.org/packages/37/3a/b46a0116b1aacbb6156b2957eb5cb928c94b49f4626eb2540ca8d16ee757/pyqt5_sip-12.18.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8372ec8704bfd5e09942d0d055a1657eb4f702f4b30847a5e59df0496f99d67f", size = 124594, upload-time = "2026-01-13T15:53:13.159Z" },
+ { url = "https://files.pythonhosted.org/packages/58/63/df3037f11391c25c5b0ab233d22e58b8f056cb1ce16d7ecadb844421ce75/pyqt5_sip-12.18.0-cp314-cp314-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdb45c7cd2af7eccd7370b994d432bfc7965079f845392760724f26771bb59dc", size = 339056, upload-time = "2026-01-13T15:53:16.558Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/e7/4f96b84520b8f8b7502682fd43f68f63ca6572b5858f56e5f61c76a54fe2/pyqt5_sip-12.18.0-cp314-cp314-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:92abe984becbde768954d6d0951f56d80a9868d2fd9e738e61fc944f0ff83dd6", size = 282439, upload-time = "2026-01-13T15:53:14.856Z" },
+ { url = "https://files.pythonhosted.org/packages/79/8e/ccdf20d373ceba83e1d1b7f818505c375208ffde4a96376dc7dbe592406c/pyqt5_sip-12.18.0-cp314-cp314-win32.whl", hash = "sha256:bd9e3c6f81346f1b08d6db02305cdee20c009b43aa083d44ee2de47a7da0e123", size = 50713, upload-time = "2026-01-13T15:53:18.634Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/21/8486ed45977be615ec5371b24b47298b1cb0e1a455b419eddd0215078dba/pyqt5_sip-12.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:6d948f1be619c645cd3bda54952bfdc1aef7c79242dccea6a6858748e61114b9", size = 59622, upload-time = "2026-01-13T15:53:17.714Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.0.3"
+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/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
+]
+
+[[package]]
+name = "pytest-cov"
+version = "7.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "coverage", extra = ["toml"] },
+ { 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 = "pytest-qt"
+version = "4.5.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "pluggy" },
+ { name = "pytest" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/d3/61/8bdec02663c18bf5016709b909411dce04a868710477dc9b9844ffcf8dd2/pytest_qt-4.5.0.tar.gz", hash = "sha256:51620e01c488f065d2036425cbc1cbcf8a6972295105fd285321eb47e66a319f", size = 128702, upload-time = "2025-07-01T17:24:39.889Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cc/d0/8339b888ad64a3d4e508fed8245a402b503846e1972c10ad60955883dcbb/pytest_qt-4.5.0-py3-none-any.whl", hash = "sha256:ed21ea9b861247f7d18090a26bfbda8fb51d7a8a7b6f776157426ff2ccf26eff", size = 37214, upload-time = "2025-07-01T17:24:38.226Z" },
+]
+
+[[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 = "pyvisa"
+version = "1.16.2"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/13/72/a50adff945e66f2161a44b243970f809de19152a2e7da0473ae8ff96d642/pyvisa-1.16.2.tar.gz", hash = "sha256:75beb93eeafe20a50be5726fa4e3a645948c93d86819f9c1f8c542efa4085a38", size = 238729, upload-time = "2026-02-27T12:14:08.54Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e4/a9/776da4f397003cca093659524c3526590522f81b642936838428c11274e6/pyvisa-1.16.2-py3-none-any.whl", hash = "sha256:54f034adafd3e8d1858d57cdafec64e920444f4b84b31c9fd17487fbad0a197a", size = 181413, upload-time = "2026-02-27T12:14:07.301Z" },
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" },
+ { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" },
+ { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" },
+ { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" },
+ { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" },
+ { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" },
+ { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" },
+ { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" },
+ { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" },
+ { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" },
+ { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" },
+ { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" },
+ { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" },
+ { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" },
+ { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" },
+ { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" },
+ { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" },
+ { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" },
+ { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" },
+ { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" },
+ { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" },
+ { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" },
+ { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" },
+ { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" },
+ { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
+]
+
+[[package]]
+name = "pyzmq"
+version = "27.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "cffi", marker = "implementation_name == 'pypy'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" },
+ { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" },
+ { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" },
+ { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" },
+ { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" },
+ { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" },
+ { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" },
+ { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" },
+ { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" },
+ { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" },
+ { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" },
+ { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" },
+ { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" },
+ { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" },
+ { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" },
+ { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" },
+ { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" },
+ { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" },
+]
+
+[[package]]
+name = "qcodes"
+version = "0.55.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "broadbean" },
+ { name = "cf-xarray" },
+ { name = "dask" },
+ { name = "h5netcdf" },
+ { name = "h5py" },
+ { name = "ipykernel" },
+ { name = "ipywidgets" },
+ { name = "jsonschema" },
+ { name = "matplotlib" },
+ { name = "networkx" },
+ { name = "numpy" },
+ { name = "opentelemetry-api" },
+ { name = "packaging" },
+ { name = "pandas" },
+ { name = "pillow" },
+ { name = "pyarrow" },
+ { name = "pyvisa" },
+ { name = "ruamel-yaml" },
+ { name = "tabulate" },
+ { name = "tqdm" },
+ { name = "typing-extensions" },
+ { name = "uncertainties" },
+ { name = "versioningit" },
+ { name = "websockets" },
+ { name = "xarray" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/06/f6/b482fe84ff76cab62c7cf967ef658b4958f384d065f89b238a9b9e618f7a/qcodes-0.55.0.tar.gz", hash = "sha256:56d07628a00087546b750ef3f92f503362f0e8a70e1685f887771e074f9293e2", size = 825296, upload-time = "2026-02-25T12:18:09.801Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/48/2c/687cd527aba1594148692a962661612f90a74a1b305e4d10ded8adaa93a8/qcodes-0.55.0-py3-none-any.whl", hash = "sha256:e5b4db0c498020b765821221cca7c50b5194f365a511b65f466c819af33acd38", size = 984458, upload-time = "2026-02-25T12:18:07.889Z" },
+]
+
+[[package]]
+name = "qtpy"
+version = "2.4.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/70/01/392eba83c8e47b946b929d7c46e0f04b35e9671f8bb6fc36b6f7945b4de8/qtpy-2.4.3.tar.gz", hash = "sha256:db744f7832e6d3da90568ba6ccbca3ee2b3b4a890c3d6fbbc63142f6e4cdf5bb", size = 66982, upload-time = "2025-02-11T15:09:25.759Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/69/76/37c0ccd5ab968a6a438f9c623aeecc84c202ab2fabc6a8fd927580c15b5a/QtPy-2.4.3-py3-none-any.whl", hash = "sha256:72095afe13673e017946cc258b8d5da43314197b741ed2890e563cf384b51aa1", size = 95045, upload-time = "2025-02-11T15:09:24.162Z" },
+]
+
+[[package]]
+name = "reactivex"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b6/af/38a4b62468e4c5bd50acf511d86fe62e65a466aa6abb55b1d59a4a9e57f3/reactivex-4.1.0.tar.gz", hash = "sha256:c7499e3c802bccaa20839b3e17355a7d939573fded3f38ba3d4796278a169a3d", size = 113482, upload-time = "2025-11-05T21:44:24.557Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ba/9e/3c2f5d3abb6c5d82f7696e1e3c69b7279049e928596ce82ed25ca97a08f3/reactivex-4.1.0-py3-none-any.whl", hash = "sha256:485750ec8d9b34bcc8ff4318971d234dc4f595058a1b4435a74aefef4b2bc9bd", size = 218588, upload-time = "2025-11-05T21:44:23.015Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "rpds-py" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "requests"
+version = "2.33.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "certifi" },
+ { name = "charset-normalizer" },
+ { name = "idna" },
+ { name = "urllib3" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" },
+]
+
+[[package]]
+name = "roman-numerals"
+version = "4.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.30.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" },
+ { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" },
+ { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" },
+ { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" },
+ { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" },
+ { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" },
+ { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" },
+ { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" },
+ { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" },
+ { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" },
+ { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" },
+ { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" },
+ { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" },
+ { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" },
+ { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" },
+ { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" },
+ { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" },
+ { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" },
+ { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" },
+ { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" },
+ { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" },
+ { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" },
+ { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" },
+ { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" },
+ { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" },
+ { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" },
+ { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" },
+ { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" },
+ { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" },
+ { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" },
+ { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" },
+ { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" },
+ { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" },
+ { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" },
+ { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" },
+ { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" },
+ { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" },
+ { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" },
+ { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" },
+ { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" },
+ { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" },
+ { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" },
+ { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" },
+ { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" },
+ { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" },
+ { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" },
+ { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" },
+ { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" },
+ { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" },
+]
+
+[[package]]
+name = "ruamel-yaml"
+version = "0.19.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.15.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" },
+ { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" },
+ { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" },
+ { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" },
+ { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" },
+ { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" },
+ { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" },
+ { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" },
+ { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" },
+ { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" },
+]
+
+[[package]]
+name = "schema"
+version = "0.7.8"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fb/2e/8da627b65577a8f130fe9dfa88ce94fcb24b1f8b59e0fc763ee61abef8b8/schema-0.7.8.tar.gz", hash = "sha256:e86cc08edd6fe6e2522648f4e47e3a31920a76e82cce8937535422e310862ab5", size = 45540, upload-time = "2025-10-11T13:15:40.281Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c9/75/aad85817266ac5285c93391711d231ca63e9ae7d42cd3ca37549e24ebe52/schema-0.7.8-py2.py3-none-any.whl", hash = "sha256:00bd977fadc7d9521bf289850cd8a8aa5f4948f575476b8daaa5c1b57af2dce1", size = 19108, upload-time = "2025-10-11T17:13:07.323Z" },
+]
+
+[[package]]
+name = "scipy"
+version = "1.17.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" },
+ { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" },
+ { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" },
+ { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" },
+ { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" },
+ { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" },
+ { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" },
+ { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" },
+ { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" },
+ { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" },
+ { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" },
+ { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" },
+ { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" },
+ { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" },
+ { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" },
+ { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" },
+ { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" },
+ { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" },
+ { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" },
+ { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" },
+ { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" },
+ { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" },
+ { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" },
+ { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" },
+ { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" },
+ { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" },
+ { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" },
+ { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" },
+ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" },
+]
+
+[[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" },
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "3.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/75/a7/9810d872919697c9d01295633f5d574fb416d47e535f258272ca1f01f447/snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895", size = 105575, upload-time = "2025-05-09T16:34:51.843Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c8/78/3565d011c61f5a43488987ee32b6f3f656e7f107ac2782dd57bdd7d91d9a/snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064", size = 103274, upload-time = "2025-05-09T16:34:50.371Z" },
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.8.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "9.0.4"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.12' and sys_platform == 'win32'",
+ "python_full_version < '3.12' and sys_platform == 'emscripten'",
+ "python_full_version < '3.12' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+dependencies = [
+ { name = "alabaster", marker = "python_full_version < '3.12'" },
+ { name = "babel", marker = "python_full_version < '3.12'" },
+ { name = "colorama", marker = "python_full_version < '3.12' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version < '3.12'" },
+ { name = "imagesize", marker = "python_full_version < '3.12'" },
+ { name = "jinja2", marker = "python_full_version < '3.12'" },
+ { name = "packaging", marker = "python_full_version < '3.12'" },
+ { name = "pygments", marker = "python_full_version < '3.12'" },
+ { name = "requests", marker = "python_full_version < '3.12'" },
+ { name = "roman-numerals", marker = "python_full_version < '3.12'" },
+ { name = "snowballstemmer", marker = "python_full_version < '3.12'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version < '3.12'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version < '3.12'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version < '3.12'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version < '3.12'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version < '3.12'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version < '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/42/50/a8c6ccc36d5eacdfd7913ddccd15a9cee03ecafc5ee2bc40e1f168d85022/sphinx-9.0.4.tar.gz", hash = "sha256:594ef59d042972abbc581d8baa577404abe4e6c3b04ef61bd7fc2acbd51f3fa3", size = 8710502, upload-time = "2025-12-04T07:45:27.343Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c6/3f/4bbd76424c393caead2e1eb89777f575dee5c8653e2d4b6afd7a564f5974/sphinx-9.0.4-py3-none-any.whl", hash = "sha256:5bebc595a5e943ea248b99c13814c1c5e10b3ece718976824ffa7959ff95fffb", size = 3917713, upload-time = "2025-12-04T07:45:24.944Z" },
+]
+
+[[package]]
+name = "sphinx"
+version = "9.1.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.15' and sys_platform == 'win32'",
+ "python_full_version == '3.14.*' and sys_platform == 'win32'",
+ "python_full_version >= '3.15' and sys_platform == 'emscripten'",
+ "python_full_version == '3.14.*' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.15' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version == '3.14.*' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'",
+ "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'",
+]
+dependencies = [
+ { name = "alabaster", marker = "python_full_version >= '3.12'" },
+ { name = "babel", marker = "python_full_version >= '3.12'" },
+ { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" },
+ { name = "docutils", marker = "python_full_version >= '3.12'" },
+ { name = "imagesize", marker = "python_full_version >= '3.12'" },
+ { name = "jinja2", marker = "python_full_version >= '3.12'" },
+ { name = "packaging", marker = "python_full_version >= '3.12'" },
+ { name = "pygments", marker = "python_full_version >= '3.12'" },
+ { name = "requests", marker = "python_full_version >= '3.12'" },
+ { name = "roman-numerals", marker = "python_full_version >= '3.12'" },
+ { name = "snowballstemmer", marker = "python_full_version >= '3.12'" },
+ { name = "sphinxcontrib-applehelp", marker = "python_full_version >= '3.12'" },
+ { name = "sphinxcontrib-devhelp", marker = "python_full_version >= '3.12'" },
+ { name = "sphinxcontrib-htmlhelp", marker = "python_full_version >= '3.12'" },
+ { name = "sphinxcontrib-jsmath", marker = "python_full_version >= '3.12'" },
+ { name = "sphinxcontrib-qthelp", marker = "python_full_version >= '3.12'" },
+ { name = "sphinxcontrib-serializinghtml", marker = "python_full_version >= '3.12'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/cd/bd/f08eb0f4eed5c83f1ba2a3bd18f7745a2b1525fad70660a1c00224ec468a/sphinx-9.1.0.tar.gz", hash = "sha256:7741722357dd75f8190766926071fed3bdc211c74dd2d7d4df5404da95930ddb", size = 8718324, upload-time = "2025-12-31T15:09:27.646Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/73/f7/b1884cb3188ab181fc81fa00c266699dab600f927a964df02ec3d5d1916a/sphinx-9.1.0-py3-none-any.whl", hash = "sha256:c84fdd4e782504495fe4f2c0b3413d6c2bf388589bb352d439b2a3bb99991978", size = 3921742, upload-time = "2025-12-31T15:09:25.561Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/ba/6e/b837e84a1a704953c62ef8776d45c3e8d759876b4a84fe14eba2859106fe/sphinxcontrib_applehelp-2.0.0.tar.gz", hash = "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", size = 20053, upload-time = "2024-07-29T01:09:00.465Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300, upload-time = "2024-07-29T01:08:58.99Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f6/d2/5beee64d3e4e747f316bae86b55943f51e82bb86ecd325883ef65741e7da/sphinxcontrib_devhelp-2.0.0.tar.gz", hash = "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", size = 12967, upload-time = "2024-07-29T01:09:23.417Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530, upload-time = "2024-07-29T01:09:21.945Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/93/983afd9aa001e5201eab16b5a444ed5b9b0a7a010541e0ddfbbfd0b2470c/sphinxcontrib_htmlhelp-2.1.0.tar.gz", hash = "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9", size = 22617, upload-time = "2024-07-29T01:09:37.889Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705, upload-time = "2024-07-29T01:09:36.407Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/b2/e8/9ed3830aeed71f17c026a07a5097edcf44b692850ef215b161b8ad875729/sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8", size = 5787, upload-time = "2019-01-21T16:10:16.347Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c2/42/4c8646762ee83602e3fb3fbe774c2fac12f317deb0b5dbeeedd2d3ba4b77/sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", size = 5071, upload-time = "2019-01-21T16:10:14.333Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/68/bc/9104308fc285eb3e0b31b67688235db556cd5b0ef31d96f30e45f2e51cae/sphinxcontrib_qthelp-2.0.0.tar.gz", hash = "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", size = 17165, upload-time = "2024-07-29T01:09:56.435Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743, upload-time = "2024-07-29T01:09:54.885Z" },
+]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3b/44/6716b257b0aa6bfd51a1b31665d1c205fb12cb5ad56de752dfa15657de2f/sphinxcontrib_serializinghtml-2.0.0.tar.gz", hash = "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d", size = 16080, upload-time = "2024-07-29T01:10:09.332Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072, upload-time = "2024-07-29T01:10:08.203Z" },
+]
+
+[[package]]
+name = "stack-data"
+version = "0.6.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "asttokens" },
+ { name = "executing" },
+ { name = "pure-eval" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" },
+]
+
+[[package]]
+name = "tabulate"
+version = "0.10.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754, upload-time = "2026-03-04T18:55:34.402Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814, upload-time = "2026-03-04T18:55:31.284Z" },
+]
+
+[[package]]
+name = "tinycss2"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "webencodings" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.4.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" },
+ { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" },
+ { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" },
+ { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" },
+ { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" },
+ { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" },
+ { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" },
+ { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" },
+ { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" },
+ { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" },
+ { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" },
+ { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" },
+ { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" },
+ { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" },
+ { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" },
+ { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" },
+ { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" },
+ { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" },
+ { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" },
+ { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" },
+ { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" },
+ { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" },
+ { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" },
+ { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" },
+ { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" },
+ { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" },
+ { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" },
+ { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" },
+ { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" },
+ { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" },
+]
+
+[[package]]
+name = "toolz"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" },
+]
+
+[[package]]
+name = "tornado"
+version = "6.5.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" },
+ { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" },
+ { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" },
+ { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" },
+ { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" },
+]
+
+[[package]]
+name = "tqdm"
+version = "4.67.3"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" },
+]
+
+[[package]]
+name = "traitlets"
+version = "5.14.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
+]
+
+[[package]]
+name = "uc-micro-py"
+version = "2.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/78/67/9a363818028526e2d4579334460df777115bdec1bb77c08f9db88f6389f2/uc_micro_py-2.0.0.tar.gz", hash = "sha256:c53691e495c8db60e16ffc4861a35469b0ba0821fe409a8a7a0a71864d33a811", size = 6611, upload-time = "2026-03-01T06:31:27.526Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/61/73/d21edf5b204d1467e06500080a50f79d49ef2b997c79123a536d4a17d97c/uc_micro_py-2.0.0-py3-none-any.whl", hash = "sha256:3603a3859af53e5a39bc7677713c78ea6589ff188d70f4fee165db88e22b242c", size = 6383, upload-time = "2026-03-01T06:31:26.257Z" },
+]
+
+[[package]]
+name = "uncertainties"
+version = "3.2.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/fe/0c/cb09f33b26955399c675ab378e4063ed7e48422d3d49f96219ab0be5eba9/uncertainties-3.2.3.tar.gz", hash = "sha256:76a5653e686f617a42922d546a239e9efce72e6b35411b7750a1d12dcba03031", size = 160492, upload-time = "2025-04-21T19:58:28.63Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8f/5e/f1e1dd319e35e962a4e00b33150a8868b6329cc1d19fd533436ba5488f09/uncertainties-3.2.3-py3-none-any.whl", hash = "sha256:313353900d8f88b283c9bad81e7d2b2d3d4bcc330cbace35403faaed7e78890a", size = 60118, upload-time = "2025-04-21T19:58:26.864Z" },
+]
+
+[[package]]
+name = "urllib3"
+version = "2.6.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" },
+]
+
+[[package]]
+name = "versioningit"
+version = "3.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "packaging" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/89/f4/bc578cc80989c572231a36cc03cc097091176fa3fb8b4e2af1deb4370eb7/versioningit-3.3.0.tar.gz", hash = "sha256:b91ad7d73e73d21220e69540f20213f2b729a1f9b35c04e9e137eaf28d2214da", size = 220280, upload-time = "2025-06-27T20:13:23.368Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/1c/59/964ecb8008722d27d8a835baea81f56a91cea8e097b3be992bc6ccde6367/versioningit-3.3.0-py3-none-any.whl", hash = "sha256:23b1db3c4756cded9bd6b0ddec6643c261e3d0c471707da3e0b230b81ce53e4b", size = 38439, upload-time = "2025-06-27T20:13:21.927Z" },
+]
+
+[[package]]
+name = "wcwidth"
+version = "0.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684, upload-time = "2026-02-06T19:19:40.919Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" },
+]
+
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
+]
+
+[[package]]
+name = "websockets"
+version = "16.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" },
+ { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" },
+ { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" },
+ { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" },
+ { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" },
+ { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" },
+ { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" },
+ { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" },
+ { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" },
+ { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" },
+ { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" },
+ { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" },
+ { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" },
+ { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" },
+ { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" },
+ { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" },
+ { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" },
+ { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" },
+ { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" },
+ { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" },
+ { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" },
+ { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" },
+ { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" },
+ { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" },
+ { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" },
+ { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" },
+ { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" },
+ { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" },
+]
+
+[[package]]
+name = "widgetsnbextension"
+version = "4.0.15"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" },
+]
+
+[[package]]
+name = "xarray"
+version = "2026.2.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "numpy" },
+ { name = "packaging" },
+ { name = "pandas" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/0f/03/e3353b72e518574b32993989d8f696277bf878e9d508c7dd22e86c0dab5b/xarray-2026.2.0.tar.gz", hash = "sha256:978b6acb018770554f8fd964af4eb02f9bcc165d4085dbb7326190d92aa74bcf", size = 3111388, upload-time = "2026-02-13T22:20:50.18Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/99/92/545eb2ca17fc0e05456728d7e4378bfee48d66433ae3b7e71948e46826fb/xarray-2026.2.0-py3-none-any.whl", hash = "sha256:e927d7d716ea71dea78a13417970850a640447d8dd2ceeb65c5687f6373837c9", size = 1405358, upload-time = "2026-02-13T22:20:47.847Z" },
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
+]