From a9f61ac933e6f8fb9103677bb8204e4665c47d92 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Tue, 18 Feb 2025 14:29:51 -0800 Subject: [PATCH 01/10] Begin setting up tooling (not ready yet) --- docs/Makefile | 2 +- pyproject.toml | 215 ++++- test/__init__.py | 0 test/cb.ipynb | 4 +- test/conftest.py | 17 + test/fc-version.py | 4 - test/test-5-instances-3-channels.py | 67 -- test/test-simple-callback-in-lab.ipynb | 4 +- test/test-simple-callback-response.py | 55 -- test/test_5_instances_3_channels.py | 98 +++ test/test_fc_version.py | 13 + test/test_simple_callback.py | 33 + test/test_simple_callback_in_lab.ipynb | 146 ++++ test/test_simple_callback_response.py | 72 ++ ...est_socket_not_added_until_listener.ipynb} | 4 +- test/test_socket_not_added_until_listener.py | 51 ++ uv.lock | 781 ++++++++++++++++++ 17 files changed, 1421 insertions(+), 145 deletions(-) create mode 100644 test/__init__.py create mode 100644 test/conftest.py delete mode 100644 test/fc-version.py delete mode 100644 test/test-5-instances-3-channels.py delete mode 100644 test/test-simple-callback-response.py create mode 100644 test/test_5_instances_3_channels.py create mode 100644 test/test_fc_version.py create mode 100644 test/test_simple_callback.py create mode 100644 test/test_simple_callback_in_lab.ipynb create mode 100644 test/test_simple_callback_response.py rename test/{test-socket-not-added-until-listener.ipynb => test_socket_not_added_until_listener.ipynb} (98%) create mode 100644 test/test_socket_not_added_until_listener.py create mode 100644 uv.lock diff --git a/docs/Makefile b/docs/Makefile index d4bb2cb..3fb41af 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build +SPHINXBUILD ?= uv run sphinx-build SOURCEDIR = . BUILDDIR = _build diff --git a/pyproject.toml b/pyproject.toml index f958fe6..115c0e5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,16 +6,11 @@ build-backend = "setuptools.build_meta" name = "firefly_client" version = "3.2.0" description = "Python API for Firefly: display astronomical data as tables, images, charts, and more!" -authors = [ - {name = "IPAC LSST SUIT"} -] +authors = [{ name = "IPAC LSST SUIT" }] readme = "README.md" -license = {file = "License.txt"} -requires-python = ">=3.8" -dependencies = [ - "websocket-client", - "requests" -] +license = { file = "License.txt" } +requires-python = ">=3.10" +dependencies = ["websocket-client", "requests"] keywords = [ "jupyter", "firefly", @@ -25,7 +20,7 @@ keywords = [ "visualization", "images", "charts", - "tables" + "tables", ] classifiers = [ "Intended Audience :: Developers", @@ -35,7 +30,7 @@ classifiers = [ "Topic :: Scientific/Engineering :: Astronomy", "Topic :: Scientific/Engineering :: Visualization", "Programming Language :: Python", - "Programming Language :: Python :: 3" + "Programming Language :: Python :: 3", ] [project.urls] @@ -48,5 +43,201 @@ docs = [ "Sphinx~=7.1.0", "sphinx-automodapi", "pydata-sphinx-theme", - "myst-parser" + "myst-parser", +] +test = [ + "pytest>=8.3.4", + "pytest-container>=0.4.3", + "pytest-xdist>=3.6.1", + "tox>=4.24.1", +] + +[tool.pytest.ini_options] +testpaths = ["firefly_client", "docs"] +doctest_plus = "enabled" +text_file_format = "rst" +addopts = "--doctest-rst" + +[tool.coverage.run] +omit = [ + "firefly_client/conftest.py", + "firefly_client/*setup_package*", + "firefly_client/tests/*", + "firefly_client/*/tests/*", + "firefly_client/extern/*", + "firefly_client/version*", + "*/firefly_client/conftest.py", + "*/firefly_client/*setup_package*", + "*/firefly_client/tests/*", + "*/firefly_client/*/tests/*", + "*/firefly_client/extern/*", + "*/firefly_client/version*", +] + +[tool.coverage.report] +exclude_lines = [ + # Have to re-enable the standard pragma + "pragma: no cover", + # Don't complain about packages we have installed + "except ImportError", + # Don't complain if tests don't hit assertions + "raise AssertionError", + "raise NotImplementedError", + # Don't complain about script hooks + "def main(.*):", + # Ignore branches that don't pertain to this version of Python + "pragma: py{ignore_python_version}", + # Don't complain about IPython completion helper + "def _ipython_key_completions_", + # typing.TYPE_CHECKING is False at runtime + "if TYPE_CHECKING:", + # Ignore typing overloads + "@overload", +] + +[tool.tox] +env_list = ["3.10", "3.11", "3.12", "3.13", "build_docs"] +requires = ["tox>=4.0", "tox-uv>=1.20", "pytest-container>=0.4.3"] + +[tool.tox.env_run_base] +runner = "uv-venv-lock-runner" +commands = [ + [ + "pytest", + "--cov=firefly-client", + "--cov-report=term-missing", + "--cov-report=xml:coverage.xml", + "--doctest-modules", + "{posargs}", + ], +] +description = "run tests with the oldest supported version of key dependencies on {base_python}" +pass_env = ["TOXENV", "CI", "CC", "LOCALE_ARCHIVE", "LC_ALL"] +set_env = { MPLBACKEND = "agg" } +dependency_groups = ["test"] +uv_python_preference = "only-managed" +allowlist_externals = ["python"] + +[tool.tox.env.build_docs] +description = "invoke sphinx-build to build the HTML docs" +change_dir = "docs" +dependency_groups = ["docs"] +commands = [ + [ + "sphinx-build", + "-j", + "auto", + "--color", + "-W", + "--keep-going", + "-b", + "html", + "-d", + "_build/.doctrees", + ".", + "_build/html", + "{posargs}", + ], +] +uv_python_preference = "only-managed" +allowlist_externals = ["python"] + + +[tool.ruff] +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "docs", ] + +# Same as Black. +line-length = 88 +indent-width = 4 + +# Assume Python 3.9 +target-version = "py311" + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F", "C90", "I", "RUF"] +ignore = ["RUF002"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +# Like Black, use double quotes for strings. +quote-style = "double" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = true + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + +[tool.ruff.lint.isort] +case-sensitive = true +combine-as-imports = true +force-wrap-aliases = true +known-local-folder = ["firefly_client"] +length-sort = true +lines-after-imports = 2 + +[tool.ruff.lint.mccabe] +# Flag errors (`C901`) whenever the complexity level exceeds 5. +max-complexity = 12 + +[tool.ruff.lint.pydoclint] +# Skip docstrings which fit on a single line. +ignore-one-line-docstrings = true + +[tool.ruff.lint.pydocstyle] +convention = "numpy" diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/cb.ipynb b/test/cb.ipynb index 624af28..23735ca 100644 --- a/test/cb.ipynb +++ b/test/cb.ipynb @@ -85,7 +85,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -99,7 +99,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.13.1" } }, "nbformat": 4, diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..0ad08f2 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,17 @@ +from pytest_container import ( + add_logging_level_options, + auto_container_parametrize, + set_logging_level_from_cli_args, +) + + +def pytest_generate_tests(metafunc): + auto_container_parametrize(metafunc) + + +def pytest_addoption(parser): + add_logging_level_options(parser) + + +def pytest_configure(config): + set_logging_level_from_cli_args(config) diff --git a/test/fc-version.py b/test/fc-version.py deleted file mode 100644 index 1ca4971..0000000 --- a/test/fc-version.py +++ /dev/null @@ -1,4 +0,0 @@ -from firefly_client import FireflyClient -import firefly_client -v_str = firefly_client.__dict__['__version__'] if '__version__' in firefly_client.__dict__ else 'development' -print('Version: %s' % v_str) diff --git a/test/test-5-instances-3-channels.py b/test/test-5-instances-3-channels.py deleted file mode 100644 index 407d973..0000000 --- a/test/test-5-instances-3-channels.py +++ /dev/null @@ -1,67 +0,0 @@ -from firefly_client import FireflyClient - - -def listener1(ev): - print('l1') - print(ev) - - -def listener2(ev): - print('l2') - print(ev) - - -def listener3(ev): - print('l3') - print(ev) - - -def listener4(ev): - print('l4') - print(ev) - - -lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly' -local_host = 'http://127.0.0.1:8080/firefly' -host = local_host -channel1 = 'channel-test-1' -channel2 = 'channel-test-2' -channel3 = 'channel-test-3' - - -print(""" - -------------------------------------------- - 5 instance of firefly that define 3 channels") - - since listeners are only added on 2 channels, only two websockets are made") - - The first 3 instances use the same channel so they share a websocket") - - instance 5 never adds a listener and therefore not websocket is made") - - The initial response on the listeners will show the number connections") - - example look for something like: channel-test: ['1b4', '1b5']") - - You can see this on the server side my monitoring firefly.log") - -------------------------------------------- -""") - -fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True) -fc1_c1.add_extension(ext_type='POINT', title='a point') -# fc.add_extension(ext_type='LINE_SELECT', title='a line') -fc2_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=False) -fc3_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=False) -fc1_c2 = FireflyClient.make_client(host, channel_override=channel2, launch_browser=True) -fc1_c2.add_extension(ext_type='POINT', title='a point') -fc1_c3 = FireflyClient.make_client(host, channel_override=channel3, launch_browser=True) -print(f'>>>>>> channel: {channel1}, firefly url: {fc1_c1.get_firefly_url()}') -print(f'>>>>>> channel: {channel2}, firefly url: {fc1_c2.get_firefly_url()}') -print(f'>>>>>> channel: {channel3}, firefly url: {fc1_c3.get_firefly_url()}') -# adding stuff but there is not listener -fc1_c3.show_fits(url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits") -fc1_c3.add_extension(ext_type='POINT', title='a point no CB') - -# ------------ add listeners -fc1_c1.add_listener(listener1) # one web socket should be made for first 3 (channel1) -fc2_c1.add_listener(listener2) -fc3_c1.add_listener(listener3) -fc1_c2.add_listener(listener4) # one web socket should be made for chanel2 - -# note - no listener added for channel3, so no websocket made - -fc1_c1.wait_for_events() diff --git a/test/test-simple-callback-in-lab.ipynb b/test/test-simple-callback-in-lab.ipynb index fc87614..eb22147 100644 --- a/test/test-simple-callback-in-lab.ipynb +++ b/test/test-simple-callback-in-lab.ipynb @@ -124,7 +124,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -138,7 +138,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.13.1" } }, "nbformat": 4, diff --git a/test/test-simple-callback-response.py b/test/test-simple-callback-response.py deleted file mode 100644 index 7ba67ab..0000000 --- a/test/test-simple-callback-response.py +++ /dev/null @@ -1,55 +0,0 @@ -from firefly_client import FireflyClient -import firefly_client -import time - -lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly' -local_host = 'http://127.0.0.1:8080/firefly' -fd = 'https://fireflydev.ipac.caltech.edu/firefly' -irsa_host = 'https://irsa.ipac.caltech.edu/irsaviewer' -data_lsst_host = 'https://data.lsst.cloud/portal/app/' -# host = 'https://fireflydev.ipac.caltech.edu/firefly-multi-ticket/firefly/' -host = local_host -channel1 = 'channel-test-1' - -v_str = firefly_client.__dict__['__version__'] if '__version__' in firefly_client.__dict__ else 'development' -print('Version: %s' % v_str) -FireflyClient._debug = False -token = None -# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True, token=token) -fc = FireflyClient.make_client(host, launch_browser=True, token=token) -print(fc.get_firefly_url()) -fc.show_fits(url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits") - - -def example_listener(ev): - if False: - print(ev) - if 'data' not in ev: - print('no data found in ev') - return - data = ev['data'] - if 'payload' in data: - print(data['payload']) - if 'type' in data: - print(data['type']) - if data['type'] == 'POINT': - print(' plotId: ' + data['plotId']) - print(' image point: %s' % data['ipt']) - print(' world point: %s' % data['wpt']) - if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT': - print(' plotId: ' + data['plotId']) - print(' image points: %s to %s' % (data['ipt0'], data['ipt1'])) - print(' world points: %s to %s' % (data['wpt0'], data['wpt1'])) - - -fc.add_extension(ext_type='POINT', title='Output Selected Point', shortcut_key='ctrl-p') -fc.add_extension(ext_type='LINE_SELECT', title='Output Selected line', shortcut_key='meta-b') -fc.add_extension(ext_type='AREA_SELECT', title='Output Selected Area', shortcut_key='a') -# ------------ add listener and wait -fc.add_listener(example_listener) -print('listener is added') -# time.sleep(3) -# fc.remove_listener(example_listener) -# time.sleep(2) -# fc.add_listener(example_listener) -fc.wait_for_events() \ No newline at end of file diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py new file mode 100644 index 0000000..0b0043f --- /dev/null +++ b/test/test_5_instances_3_channels.py @@ -0,0 +1,98 @@ +from firefly_client import FireflyClient +from pytest_container.container import Container, ContainerData, EntrypointSelection +from pytest_container.inspect import NetworkProtocol, PortForwarding + +FIREFLY_CONTAINER = Container( + url="docker.io/irsa/firefly:latest", + extra_launch_args=["--memory=4g"], + singleton=True, + entry_point=EntrypointSelection.AUTO, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=8080, + bind_ip="127.0.0.1", + ) + ], +) + +CONTAINER_IMAGES = [FIREFLY_CONTAINER] + + +def test_5_instances_3_channels(auto_container: ContainerData): + assert auto_container.forwarded_ports[0].host_port == 8080 + + def listener1(ev): + print("l1") + print(ev) + + def listener2(ev): + print("l2") + print(ev) + + def listener3(ev): + print("l3") + print(ev) + + def listener4(ev): + print("l4") + print(ev) + + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + channel1 = "channel-test-1" + channel2 = "channel-test-2" + channel3 = "channel-test-3" + + print( + """ + -------------------------------------------- + 5 instance of firefly that define 3 channels") + - since listeners are only added on 2 channels, only two websockets are made") + - The first 3 instances use the same channel so they share a websocket") + - instance 5 never adds a listener and therefore not websocket is made") + - The initial response on the listeners will show the number connections") + - example look for something like: channel-test: ['1b4', '1b5']") + - You can see this on the server side my monitoring firefly.log") + -------------------------------------------- + """ + ) + + fc1_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=True + ) + fc1_c1.add_extension(ext_type="POINT", title="a point") + # fc.add_extension(ext_type='LINE_SELECT', title='a line') + fc2_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc3_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc1_c2 = FireflyClient.make_client( + host, channel_override=channel2, launch_browser=True + ) + fc1_c2.add_extension(ext_type="POINT", title="a point") + fc1_c3 = FireflyClient.make_client( + host, channel_override=channel3, launch_browser=True + ) + print(f">>>>>> channel: {channel1}, firefly url: {fc1_c1.get_firefly_url()}") + print(f">>>>>> channel: {channel2}, firefly url: {fc1_c2.get_firefly_url()}") + print(f">>>>>> channel: {channel3}, firefly url: {fc1_c3.get_firefly_url()}") + # adding stuff but there is not listener + fc1_c3.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" + ) + fc1_c3.add_extension(ext_type="POINT", title="a point no CB") + + # ------------ add listeners + fc1_c1.add_listener( + listener1 + ) # one web socket should be made for first 3 (channel1) + fc2_c1.add_listener(listener2) + fc3_c1.add_listener(listener3) + fc1_c2.add_listener(listener4) # one web socket should be made for channel2 + + # note - no listener added for channel3, so no websocket made + + fc1_c1.wait_for_events() diff --git a/test/test_fc_version.py b/test/test_fc_version.py new file mode 100644 index 0000000..039fec0 --- /dev/null +++ b/test/test_fc_version.py @@ -0,0 +1,13 @@ +import firefly_client +from firefly_client import FireflyClient + + +def test_version(): + v_str = ( + firefly_client.__dict__["__version__"] + if "__version__" in firefly_client.__dict__ + else "development" + ) + print("Version: %s" % v_str) + assert v_str is not None + assert isinstance(v_str, str) diff --git a/test/test_simple_callback.py b/test/test_simple_callback.py new file mode 100644 index 0000000..ce891d6 --- /dev/null +++ b/test/test_simple_callback.py @@ -0,0 +1,33 @@ +from firefly_client import FireflyClient +from pytest_container.container import Container, ContainerData, EntrypointSelection +from pytest_container.inspect import NetworkProtocol, PortForwarding + +FIREFLY_CONTAINER = Container( + url="docker.io/irsa/firefly:latest", + extra_launch_args=["--memory=4g"], + singleton=True, + entry_point=EntrypointSelection.AUTO, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=8080, + bind_ip="127.0.0.1", + ) + ], +) + +CONTAINER_IMAGES = [FIREFLY_CONTAINER] + + +def test_simple_callback(auto_container: ContainerData): + assert auto_container.forwarded_ports[0].host_port == 8080 + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + channel1 = "channel-test-1" + FireflyClient._debug = False + fc = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + # fc = FireflyClient.make_lab_client(start_tab=False) + firefly_url = fc.get_firefly_url() + assert host in firefly_url diff --git a/test/test_simple_callback_in_lab.ipynb b/test/test_simple_callback_in_lab.ipynb new file mode 100644 index 0000000..fc87614 --- /dev/null +++ b/test/test_simple_callback_in_lab.ipynb @@ -0,0 +1,146 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import FireflyClient" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", + "\n", + "# some host\n", + "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", + "local_host = 'http://127.0.0.1:8080/firefly'\n", + "irsa = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", + "fd = 'https://fireflydev.ipac.caltech.edu/firefly'\n", + "\n", + "#host = 'http://127.0.0.1:8080/suit'\n", + "host = local_host\n", + "channel1 = 'channel-test-1'\n", + "FireflyClient._debug = False\n", + "#fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", + "fc = FireflyClient.make_lab_client(start_browser_tab=False, start_tab=True, verbose=True )\n", + "#fc = FireflyClient.make_lab_client(start_tab=False)\n", + "fc.get_firefly_url()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fc.get_firefly_url()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\", plot_id='x2')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fc.set_stretch('x1', stype='zscale')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Extensions can be made but there is not web socket connections until a listener is added\n", + "\n", + "fc.add_extension(ext_type='LINE_SELECT', title='a line', shortcut_key='meta-e')\n", + "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", + "fc.add_extension(ext_type='POINT', title='a point')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# A Web socket should not be made until this cell is added\n", + "\n", + "def listener1(ev):\n", + " if False:\n", + " print(ev)\n", + " if 'data' not in ev:\n", + " print('no data found in ev')\n", + " return\n", + " data = ev['data']\n", + " if 'payload' in data:\n", + " print(data['payload'])\n", + " if 'type' in data:\n", + " print(data['type'])\n", + " if data['type'] == 'POINT':\n", + " print(' image point: %s' % data['ipt'])\n", + " print(' world point: %s' % data['wpt'])\n", + " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", + " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", + " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + "\n", + "\n", + " \n", + "fc.add_listener(listener1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from firefly_client import __version__ as v\n", + "v" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.9" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/test/test_simple_callback_response.py b/test/test_simple_callback_response.py new file mode 100644 index 0000000..a519222 --- /dev/null +++ b/test/test_simple_callback_response.py @@ -0,0 +1,72 @@ +from firefly_client import FireflyClient +from pytest_container.container import Container, ContainerData, EntrypointSelection +from pytest_container.inspect import NetworkProtocol, PortForwarding + + +FIREFLY_CONTAINER = Container( + url="docker.io/irsa/firefly:latest", + extra_launch_args=["--memory=4g"], + singleton=True, + entry_point=EntrypointSelection.AUTO, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=8080, + bind_ip="127.0.0.1", + ) + ], +) + +CONTAINER_IMAGES = [FIREFLY_CONTAINER] + + +def test_simple_callback_response(auto_container: ContainerData): + assert auto_container.forwarded_ports[0].host_port == 8080 + FireflyClient._debug = False + token = None + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + # fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True, token=token) + fc = FireflyClient.make_client(host, launch_browser=False, token=token) + print(fc.get_firefly_url()) + fc.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" + ) + + def example_listener(ev): + if False: + print(ev) + if "data" not in ev: + print("no data found in ev") + return + data = ev["data"] + if "payload" in data: + print(data["payload"]) + if "type" in data: + print(data["type"]) + if data["type"] == "POINT": + print(" plotId: " + data["plotId"]) + print(" image point: %s" % data["ipt"]) + print(" world point: %s" % data["wpt"]) + if data["type"] == "LINE_SELECT" or data["type"] == "AREA_SELECT": + print(" plotId: " + data["plotId"]) + print(" image points: %s to %s" % (data["ipt0"], data["ipt1"])) + print(" world points: %s to %s" % (data["wpt0"], data["wpt1"])) + + fc.add_extension( + ext_type="POINT", title="Output Selected Point", shortcut_key="ctrl-p" + ) + fc.add_extension( + ext_type="LINE_SELECT", title="Output Selected line", shortcut_key="meta-b" + ) + fc.add_extension( + ext_type="AREA_SELECT", title="Output Selected Area", shortcut_key="a" + ) + # ------------ add listener and wait + fc.add_listener(example_listener) + print("listener is added") + # time.sleep(3) + # fc.remove_listener(example_listener) + # time.sleep(2) + # fc.add_listener(example_listener) + fc.wait_for_events() diff --git a/test/test-socket-not-added-until-listener.ipynb b/test/test_socket_not_added_until_listener.ipynb similarity index 98% rename from test/test-socket-not-added-until-listener.ipynb rename to test/test_socket_not_added_until_listener.ipynb index 3a18baf..8be6285 100644 --- a/test/test-socket-not-added-until-listener.ipynb +++ b/test/test_socket_not_added_until_listener.ipynb @@ -126,7 +126,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -140,7 +140,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.13.1" } }, "nbformat": 4, diff --git a/test/test_socket_not_added_until_listener.py b/test/test_socket_not_added_until_listener.py new file mode 100644 index 0000000..ae7ec3b --- /dev/null +++ b/test/test_socket_not_added_until_listener.py @@ -0,0 +1,51 @@ +import time + +from firefly_client import FireflyClient +from pytest_container.container import Container, ContainerData, EntrypointSelection +from pytest_container.inspect import NetworkProtocol, PortForwarding + + +FIREFLY_CONTAINER = Container( + url="docker.io/irsa/firefly:latest", + extra_launch_args=["--memory=4g"], + singleton=True, + entry_point=EntrypointSelection.AUTO, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=8080, + bind_ip="127.0.0.1", + ) + ], +) + +CONTAINER_IMAGES = [FIREFLY_CONTAINER] + + +def test_socket_not_added_until_listener(auto_container: ContainerData): + assert auto_container.forwarded_ports[0].host_port == 8080 + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + + channel1 = "channel-test-1" + + fc1_c1 = FireflyClient.make_client( + host, channel_override=channel1, launch_browser=False + ) + fc1_c1.show_fits( + url="http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits" + ) + + fc1_c1.add_extension(ext_type="LINE_SELECT", title="a line") + fc1_c1.add_extension(ext_type="AREA_SELECT", title="a area") + fc1_c1.add_extension(ext_type="POINT", title="a point") + + def listener1(ev): + print("l1") + print(ev) + + fc1_c1.add_listener(listener1) + time.sleep(1) + fc1_c1.remove_listener(listener1) + time.sleep(1) + fc1_c1.disconnect() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..ee052eb --- /dev/null +++ b/uv.lock @@ -0,0 +1,781 @@ +version = 1 +revision = 1 +requires-python = ">=3.10" + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/3f/95338030883d8c8b91223b4e21744b04d11b161a3ef117295d8241f50ab4/accessible_pygments-0.0.5-py3-none-any.whl", hash = "sha256:88ae3211e68a1d0b011504b2ffc1691feafce124b845bd072ab6f9f66f34d4b7", size = 1395903 }, +] + +[[package]] +name = "alabaster" +version = "0.7.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/3e/13dd8e5ed9094e734ac430b5d0eb4f2bb001708a8b7856cbf8e084e001ba/alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", size = 23776 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/3c/adaf39ce1fb4afdd21b611e3d530b183bb7759c9b673d60db0e347fd4439/beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b", size = 619516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, +] + +[[package]] +name = "cachetools" +version = "5.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/74/57df1ab0ce6bc5f6fa868e08de20df8ac58f9c44330c7671ad922d2bbeae/cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95", size = 28044 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/4e/de4ff18bcf55857ba18d3a4bd48c8a9fde6bb0980c9d20b263f05387fd88/cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb", size = 9530 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 }, + { url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 }, + { url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 }, + { url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 }, + { url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 }, + { url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 }, + { url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 }, + { url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 }, + { url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 }, + { url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 }, + { url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 }, + { url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 }, + { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 }, + { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 }, + { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 }, + { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 }, + { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 }, + { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 }, + { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 }, + { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 }, + { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 }, + { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 }, + { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 }, + { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 }, + { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 }, + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "deprecation" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/d3/8ae2869247df154b64c1884d7346d412fed0c49df84db635aab2d1c40e62/deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff", size = 173788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/c3/253a89ee03fc9b9682f1541728eb66db7db22148cd94f89ab22528cd1e1b/deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a", size = 11178 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "docutils" +version = "0.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/53/a5da4f2c5739cf66290fac1431ee52aff6851c7c8ffd8264f13affd7bcdd/docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b", size = 2058365 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/87/f238c0670b94533ac0353a4e2a1a771a0cc73277b88bff23d3ae35a256c1/docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", size = 572666 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "execnet" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, +] + +[[package]] +name = "filelock" +version = "3.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 }, +] + +[[package]] +name = "firefly-client" +version = "3.2.0" +source = { editable = "." } +dependencies = [ + { name = "requests" }, + { name = "websocket-client" }, +] + +[package.optional-dependencies] +docs = [ + { name = "myst-parser" }, + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, + { name = "sphinx-automodapi" }, +] +test = [ + { name = "pytest" }, + { name = "pytest-container" }, + { name = "pytest-xdist" }, + { name = "tox" }, +] + +[package.metadata] +requires-dist = [ + { name = "myst-parser", marker = "extra == 'docs'" }, + { name = "pydata-sphinx-theme", marker = "extra == 'docs'" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.4" }, + { name = "pytest-container", marker = "extra == 'test'", specifier = ">=0.4.3" }, + { name = "pytest-xdist", marker = "extra == 'test'", specifier = ">=3.6.1" }, + { name = "requests" }, + { name = "sphinx", marker = "extra == 'docs'", specifier = "~=7.1.0" }, + { name = "sphinx-automodapi", marker = "extra == 'docs'" }, + { name = "tox", marker = "extra == 'test'", specifier = ">=4.24.1" }, + { name = "websocket-client" }, +] +provides-extras = ["docs", "test"] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/84/62473fb57d61e31fef6e36d64a179c8781605429fd927b5dd608c997be31/imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a", size = 1280026 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/62/85c4c919272577931d407be5ba5d71c20f0b616d31a0befe0ae45bb79abd/imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", size = 8769 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jinja2" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/af/92/b3130cbbf5591acf9ade8708c365f3238046ac7cb8ccba6e81abccb0ccff/jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", size = 244674 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357 }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393 }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732 }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866 }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964 }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977 }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366 }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091 }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065 }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514 }, + { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353 }, + { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392 }, + { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984 }, + { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120 }, + { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032 }, + { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057 }, + { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359 }, + { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306 }, + { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094 }, + { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521 }, + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mdit-py-plugins" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/03/a2ecab526543b152300717cf232bb4bb8605b6edb946c845016fa9c9c9fd/mdit_py_plugins-0.4.2.tar.gz", hash = "sha256:5f2cd1fdb606ddf152d37ec30e46101a60512bc0e5fa1a7002c36647b09e26b5", size = 43542 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/f7/7782a043553ee469c1ff49cfa1cdace2d6bf99a1f333cf38676b3ddf30da/mdit_py_plugins-0.4.2-py3-none-any.whl", hash = "sha256:0c673c3f889399a33b95e88d2f0d111b4447bdfea7f237dab2d488f459835636", size = 55316 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, +] + +[[package]] +name = "myst-parser" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "docutils" }, + { name = "jinja2" }, + { name = "markdown-it-py" }, + { name = "mdit-py-plugins" }, + { name = "pyyaml" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/a5/9626ba4f73555b3735ad86247a8077d4603aa8628537687c839ab08bfe44/myst_parser-4.0.1.tar.gz", hash = "sha256:5cfea715e4f3574138aecbf7d54132296bfd72bb614d31168f48c477a830a7c4", size = 93985 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pydata-sphinx-theme" +version = "0.16.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "accessible-pygments" }, + { name = "babel" }, + { name = "beautifulsoup4" }, + { name = "docutils" }, + { name = "pygments" }, + { name = "sphinx" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264 }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131 }, +] + +[[package]] +name = "pytest" +version = "8.3.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, +] + +[[package]] +name = "pytest-container" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecation" }, + { name = "filelock" }, + { name = "pytest" }, + { name = "pytest-testinfra" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/8d/443c8274fc765b58256ff92bb62a9a4359ac450ee5dc3f1b11b9babab7b4/pytest_container-0.4.3.tar.gz", hash = "sha256:4b9fb1eb398ebbd596cc50b5c7ca9eb7508363ebfda779bcacab3ea131c2866f", size = 40298 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/04/99e05194194e3f21bc627e700e1edc1aea91f1161bbb32728bea5c73c128/pytest_container-0.4.3-py3-none-any.whl", hash = "sha256:94be6bd43ece7a6a9fa7781d8497f96db2f7d77864addbe2d57ee59a41649721", size = 44127 }, +] + +[[package]] +name = "pytest-testinfra" +version = "10.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/15/354adf3dd9635b554b27eb0167216e02bebed1796035991999c32b5081af/pytest-testinfra-10.1.1.tar.gz", hash = "sha256:a876f1453a01b58d94d9d936dd50344c2c01ac7880a2b41d15bdf233aed9cf1f", size = 86567 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/a8/e66b4ef547c8ff86b9c3a4744c296b879a2c6e98f4796f4bee66b911762b/pytest_testinfra-10.1.1-py3-none-any.whl", hash = "sha256:b990dc7d77b49a1bba24818fbff49b6171d8c46d606fb5ca86b937de690d7062", size = 76765 }, +] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "execnet" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/c4/3c310a19bc1f1e9ef50075582652673ef2bfc8cd62afef9585683821902f/pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d", size = 84060 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/7b/af302bebf22c749c56c9c3e8ae13190b5b5db37a33d9068652e8f73b7089/snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", size = 86699 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/dc/c02e01294f7265e63a7315fe086dd1df7dacb9f840a804da846b96d01b96/snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a", size = 93002 }, +] + +[[package]] +name = "soupsieve" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/ce/fbaeed4f9fb8b2daa961f90591662df6a86c1abf25c548329a86920aedfb/soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb", size = 101569 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c2/fe97d779f3ef3b15f05c94a2f1e3d21732574ed441687474db9d342a7315/soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9", size = 36186 }, +] + +[[package]] +name = "sphinx" +version = "7.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "alabaster" }, + { name = "babel" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "docutils" }, + { name = "imagesize" }, + { name = "jinja2" }, + { name = "packaging" }, + { name = "pygments" }, + { name = "requests" }, + { name = "snowballstemmer" }, + { name = "sphinxcontrib-applehelp" }, + { name = "sphinxcontrib-devhelp" }, + { name = "sphinxcontrib-htmlhelp" }, + { name = "sphinxcontrib-jsmath" }, + { name = "sphinxcontrib-qthelp" }, + { name = "sphinxcontrib-serializinghtml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/01/688bdf9282241dca09fe6e3a1110eda399fa9b10d0672db609e37c2e7a39/sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f", size = 6828258 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/17/325cf6a257d84751a48ae90752b3d8fe0be8f9535b6253add61c49d0d9bc/sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe", size = 3169543 }, +] + +[[package]] +name = "sphinx-automodapi" +version = "0.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/38/ffc5c1dc26776f1d037021ebcffdfaca74644ec5e5cf5a7735149c51df90/sphinx_automodapi-0.18.0.tar.gz", hash = "sha256:7bf9d9a2cb67a5389c51071cfd86674ca3892ca5d5943f95de4553d6f35dddae", size = 51297 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/96/5492f177d6bb95c5418315ab1c517283f06895b4066f2837ba215be42c84/sphinx_automodapi-0.18.0-py3-none-any.whl", hash = "sha256:022860385590768f52d4f6e19abb83b2574772d2721fb4050ecdb6e593a1a440", size = 88516 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/85/9ebeae2f76e9e77b952f4b274c27238156eae7979c5421fba91a28f4970d/sphinxcontrib_applehelp-2.0.0-py3-none-any.whl", hash = "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5", size = 119300 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/7a/987e583882f985fe4d7323774889ec58049171828b58c2217e7f79cdf44e/sphinxcontrib_devhelp-2.0.0-py3-none-any.whl", hash = "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2", size = 82530 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/7b/18a8c0bcec9182c05a0b3ec2a776bba4ead82750a55ff798e8d406dae604/sphinxcontrib_htmlhelp-2.1.0-py3-none-any.whl", hash = "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", size = 98705 }, +] + +[[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 } +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 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/83/859ecdd180cacc13b1f7e857abf8582a64552ea7a061057a6c716e790fce/sphinxcontrib_qthelp-2.0.0-py3-none-any.whl", hash = "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb", size = 88743 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, +] + +[[package]] +name = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, + { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, + { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, + { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, + { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, + { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, + { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, + { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, + { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, + { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, + { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, + { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, + { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, + { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, + { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, + { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, + { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, + { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, + { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, + { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, + { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, + { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, + { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, + { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, + { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, + { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, + { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, + { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, + { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, + { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, +] + +[[package]] +name = "tox" +version = "4.24.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/7b/97f757e159983737bdd8fb513f4c263cd411a846684814ed5433434a1fa9/tox-4.24.1.tar.gz", hash = "sha256:083a720adbc6166fff0b7d1df9d154f9d00bfccb9403b8abf6bc0ee435d6a62e", size = 194742 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/04/b0d1c1b44c98583cab9eabb4acdba964fdf6b6c597c53cfb8870fd08cbbf/tox-4.24.1-py3-none-any.whl", hash = "sha256:57ba7df7d199002c6df8c2db9e6484f3de6ca8f42013c083ea2d4d1e5c6bdc75", size = 171829 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "virtualenv" +version = "20.29.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/88/dacc875dd54a8acadb4bcbfd4e3e86df8be75527116c91d8f9784f5e9cab/virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728", size = 4320272 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826 }, +] From 07fda275bb000d3753e6a70eea2949c61f4f1e9b Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Tue, 18 Feb 2025 16:44:47 -0800 Subject: [PATCH 02/10] Start moving the examples to proper tests --- examples/basic-demo-tableload.ipynb | 16 +- examples/demo-3color.ipynb | 92 +- examples/demo-HiPS.ipynb | 56 +- examples/demo-advanced-all.ipynb | 425 +++---- examples/demo-advanced-steps.ipynb | 221 ++-- examples/demo-advanced-table-images.ipynb | 23 +- .../demo-advanced-tables-images-upload.ipynb | 31 +- examples/demo-basic.ipynb | 61 +- examples/demo-lsst-footprint.ipynb | 34 +- examples/demo-region.ipynb | 29 +- examples/demo-show-image.ipynb | 10 +- examples/firefly_slate_demo.py | 238 ++-- examples/multi-moc.py | 24 +- examples/plot-interface.ipynb | 40 +- firefly_client/env.py | 105 +- firefly_client/fc_utils.py | 144 ++- firefly_client/ffws.py | 130 +- firefly_client/firefly_client.py | 1101 +++++++++++------ firefly_client/plot.py | 190 ++- firefly_client/range_values.py | 201 ++- pyproject.toml | 6 +- test/cb.ipynb | 41 +- test/test-simple-callback-in-lab.ipynb | 62 +- test/test_basic_tableload.py | 44 + test/test_simple_callback_in_lab.ipynb | 62 +- ...test_socket_not_added_until_listener.ipynb | 31 +- uv.lock | 181 +++ 27 files changed, 2323 insertions(+), 1275 deletions(-) create mode 100644 test/test_basic_tableload.py diff --git a/examples/basic-demo-tableload.ipynb b/examples/basic-demo-tableload.ipynb index 0cb692c..963da68 100644 --- a/examples/basic-demo-tableload.ipynb +++ b/examples/basic-demo-tableload.ipynb @@ -52,7 +52,7 @@ "metadata": {}, "outputs": [], "source": [ - "url='https://irsa.ipac.caltech.edu/irsaviewer'\n", + "url = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", "# url=http://127.0.0.1:8080/firefly # if you have firefly server running locally\n", "\n", "fc = FireflyClient.make_client(url)" @@ -89,7 +89,9 @@ "source": [ "import os\n", "\n", - "testdata_repo_path = '/hydra/cm' # to be reset to where test files are located on your system" + "testdata_repo_path = (\n", + " \"/hydra/cm\" # to be reset to where test files are located on your system\n", + ")" ] }, { @@ -112,7 +114,7 @@ "metadata": {}, "outputs": [], "source": [ - "localfile = os.path.join(testdata_repo_path, 'MF.20210502.18830.fits')\n", + "localfile = os.path.join(testdata_repo_path, \"MF.20210502.18830.fits\")\n", "filename = fc.upload_file(localfile)\n", "\n", "fc.show_table(filename)" @@ -154,10 +156,10 @@ "metadata": {}, "outputs": [], "source": [ - "localfile = os.path.join(testdata_repo_path, 'Mr31objsearch.xml')\n", + "localfile = os.path.join(testdata_repo_path, \"Mr31objsearch.xml\")\n", "filename = fc.upload_file(localfile)\n", "\n", - "fc.show_table(filename, tbl_id='votable-0')" + "fc.show_table(filename, tbl_id=\"votable-0\")" ] }, { @@ -173,7 +175,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_table(filename, tbl_id='votable-1', table_index=1)" + "fc.show_table(filename, tbl_id=\"votable-1\", table_index=1)" ] }, { @@ -201,7 +203,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.13.2" } }, "nbformat": 4, diff --git a/examples/demo-3color.ipynb b/examples/demo-3color.ipynb index 70067a1..ef9ab27 100644 --- a/examples/demo-3color.ipynb +++ b/examples/demo-3color.ipynb @@ -7,10 +7,11 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", "using_lab = False\n", - "#url='https://irsa.ipac.caltech.edu/irsaviewer'\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "# url='https://irsa.ipac.caltech.edu/irsaviewer'\n", + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -30,44 +31,45 @@ "source": [ "# Add a three color fits\n", "# in cell 0, 0, 1, 1\n", - "target = '210.80227;54.34895;EQ_J2000' #Galaxy M101\n", - "viewer_id = '3C'\n", - "r = fc.add_cell(0, 0, 1, 1, 'images', viewer_id)\n", - "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", - "if r['success']:\n", - " threeC= [\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : '3a',\n", - " 'SurveyKeyBand': '3',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '.14'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : '3a',\n", - " 'SurveyKeyBand': '2',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '.14'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : '3a',\n", - " 'SurveyKeyBand': '1',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '.14'\n", - " }]\n", - " \n", - " fc.show_fits_3color(threeC, plot_id='wise_m101', viewer_id=viewer_id)" + "target = \"210.80227;54.34895;EQ_J2000\" # Galaxy M101\n", + "viewer_id = \"3C\"\n", + "r = fc.add_cell(0, 0, 1, 1, \"images\", viewer_id)\n", + "rv = \"92,-2,92,8,NaN,2,44,25,600,120\"\n", + "if r[\"success\"]:\n", + " threeC = [\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"3\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \".14\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"2\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \".14\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"1\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \".14\",\n", + " },\n", + " ]\n", + "\n", + " fc.show_fits_3color(threeC, plot_id=\"wise_m101\", viewer_id=viewer_id)" ] }, { @@ -91,7 +93,7 @@ ], "source": [ "# Set stretch using hue-preserving algorithm with scaling coefficients .15 for RED, 1.0 for GREEN, and .4 for BLUE.\n", - "fc.set_stretch_hprgb(plot_id='wise_m101', asinh_q_value=4.2, scaling_k=[.15,1,.4])" + "fc.set_stretch_hprgb(plot_id=\"wise_m101\", asinh_q_value=4.2, scaling_k=[0.15, 1, 0.4])" ] }, { @@ -113,7 +115,7 @@ ], "source": [ "# Set per-band stretch\n", - "fc.set_stretch(plot_id='wise_m101', stype='ztype', algorithm='asinh', band='ALL')" + "fc.set_stretch(plot_id=\"wise_m101\", stype=\"ztype\", algorithm=\"asinh\", band=\"ALL\")" ] }, { @@ -134,7 +136,7 @@ ], "source": [ "# Set contrast and bias for each band\n", - "fc.set_rgb_colors(plot_id='wise_m101', bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2]) " + "fc.set_rgb_colors(plot_id=\"wise_m101\", bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2])" ] }, { @@ -155,7 +157,7 @@ ], "source": [ "# Set to use red and blue band only, with default bias and contrast\n", - "fc.set_rgb_colors(plot_id='wise_m101', use_green=False) " + "fc.set_rgb_colors(plot_id=\"wise_m101\", use_green=False)" ] }, { diff --git a/examples/demo-HiPS.ipynb b/examples/demo-HiPS.ipynb index e478c27..e6db737 100644 --- a/examples/demo-HiPS.ipynb +++ b/examples/demo-HiPS.ipynb @@ -30,9 +30,10 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -48,7 +49,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n" + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" ] }, { @@ -57,20 +58,20 @@ "metadata": {}, "outputs": [], "source": [ - "target='148.892;69.0654;EQ_J2000'\n", - "fov_deg=0.5\n", - "size=0.2\n", + "target = \"148.892;69.0654;EQ_J2000\"\n", + "fov_deg = 0.5\n", + "size = 0.2\n", "\n", "image_basic_req = {\n", - " 'Service': 'WISE',\n", - " 'Title': 'Wise',\n", - " 'SurveyKey': '3a',\n", - " 'SurveyKeyBand': '2'\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"Wise\",\n", + " \"SurveyKey\": \"3a\",\n", + " \"SurveyKeyBand\": \"2\",\n", "}\n", "\n", "hips_basic_req = {\n", - " 'title': 'A HiPS - 0.2',\n", - " 'hipsRootUrl': 'http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1'\n", + " \"title\": \"A HiPS - 0.2\",\n", + " \"hipsRootUrl\": \"http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1\",\n", "}" ] }, @@ -94,12 +95,17 @@ "metadata": {}, "outputs": [], "source": [ - "viewer_id = 'hipsDIV1'\n", - "r = fc.add_cell(0, 0, 4, 2, 'images', viewer_id)\n", - "if r['success']:\n", - " hips_url = 'http://alasky.u-strasbg.fr/DSS/DSSColor';\n", - " status = fc.show_hips(viewer_id=viewer_id, plot_id='aHipsID1-1', hips_root_url = hips_url, \n", - " Title='HiPS-DSS', WorldPt=target)" + "viewer_id = \"hipsDIV1\"\n", + "r = fc.add_cell(0, 0, 4, 2, \"images\", viewer_id)\n", + "if r[\"success\"]:\n", + " hips_url = \"http://alasky.u-strasbg.fr/DSS/DSSColor\"\n", + " status = fc.show_hips(\n", + " viewer_id=viewer_id,\n", + " plot_id=\"aHipsID1-1\",\n", + " hips_root_url=hips_url,\n", + " Title=\"HiPS-DSS\",\n", + " WorldPt=target,\n", + " )" ] }, { @@ -115,7 +121,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.set_color('aHipsID1-1', colormap_id=6, bias=0.6, contrast=1.5)" + "fc.set_color(\"aHipsID1-1\", colormap_id=6, bias=0.6, contrast=1.5)" ] }, { @@ -131,10 +137,10 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "hips_url = 'http://alasky.u-strasbg.fr/DSS/DSS2Merged';\n", - "status = fc.show_hips(plot_id='aHipsID1-2', hips_root_url = hips_url, \n", - " Title='HiPS-DSS2', WorldPt=target)" + "hips_url = \"http://alasky.u-strasbg.fr/DSS/DSS2Merged\"\n", + "status = fc.show_hips(\n", + " plot_id=\"aHipsID1-2\", hips_root_url=hips_url, Title=\"HiPS-DSS2\", WorldPt=target\n", + ")" ] }, { @@ -143,8 +149,8 @@ "metadata": {}, "outputs": [], "source": [ - "hips_url = 'http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1'\n", - "status = fc.show_hips(plot_id='aHipsID2', hips_root_url=hips_url)" + "hips_url = \"http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1\"\n", + "status = fc.show_hips(plot_id=\"aHipsID2\", hips_root_url=hips_url)" ] } ], diff --git a/examples/demo-advanced-all.ipynb b/examples/demo-advanced-all.ipynb index 95991a2..b23bbb4 100644 --- a/examples/demo-advanced-all.ipynb +++ b/examples/demo-advanced-all.ipynb @@ -24,9 +24,10 @@ "source": [ "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", "using_lab = True\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -43,7 +44,7 @@ "outputs": [], "source": [ "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "fc.change_triview_layout( FireflyClient.BIVIEW_T_IChCov)" + "fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov)" ] }, { @@ -53,7 +54,7 @@ "outputs": [], "source": [ "import os\n", - "#os.environ" + "# os.environ" ] }, { @@ -78,11 +79,11 @@ "metadata": {}, "outputs": [], "source": [ - "target = '10.68479;41.26906;EQ_J2000' #Galaxy M31\n", + "target = \"10.68479;41.26906;EQ_J2000\" # Galaxy M31\n", "# other notable targets to try\n", - "#target = '148.88822;69.06529;EQ_J2000' #Galaxy M81\n", - "#target = '202.48417;47.23056;EQ_J2000' #Galaxy M51\n", - "#target = '136.9316774;+1.1195886;galactic' # W5 star-forming region" + "# target = '148.88822;69.06529;EQ_J2000' #Galaxy M81\n", + "# target = '202.48417;47.23056;EQ_J2000' #Galaxy M51\n", + "# target = '136.9316774;+1.1195886;galactic' # W5 star-forming region" ] }, { @@ -107,205 +108,212 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "\n", - "# Add table in cell 'main'. \n", + "# Add table in cell 'main'.\n", "# 'main' is the cell id currently supported by Firefly for element type 'tables'\n", "# this cell is shown at row = 0, col = 4 with width = 2, height = 2\n", "\n", - "r = fc.add_cell(0, 4, 2, 2, 'tables', 'main')\n", + "r = fc.add_cell(0, 4, 2, 2, \"tables\", \"main\")\n", + "\n", + "if r[\"success\"]:\n", + " fc.show_table(\n", + " tbl_id=\"wiseCatTbl\",\n", + " title=\"WISE catalog\",\n", + " target_search_info={\n", + " \"catalogProject\": \"WISE\",\n", + " \"catalog\": \"allwise_p3as_psd\",\n", + " \"position\": target,\n", + " \"SearchMethod\": \"Cone\",\n", + " \"radius\": 1200,\n", + " },\n", + " options={\"removable\": True, \"showUnits\": False, \"showFilters\": True},\n", + " )\n", + "\n", "\n", - "if r['success']:\n", - " \n", - " fc.show_table(tbl_id='wiseCatTbl', title='WISE catalog', \n", - " target_search_info={'catalogProject': 'WISE', 'catalog': 'allwise_p3as_psd',\n", - " 'position': target, 'SearchMethod': 'Cone', 'radius': 1200},\n", - " options={'removable': True, 'showUnits': False, 'showFilters': True})\n", - " \n", - " \n", "# Add first chart - scatter (plot.ly direct plot)\n", - "# in cell 0, 0, 2, 2, \n", - "viewer_id = 'newChartContainer'\n", - "r = fc.add_cell(0, 0, 2, 2,'xyPlots', viewer_id)\n", + "# in cell 0, 0, 2, 2,\n", + "viewer_id = \"newChartContainer\"\n", + "r = fc.add_cell(0, 0, 2, 2, \"xyPlots\", viewer_id)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " trace1 = {\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro-w2mpro\",\n", - " 'y': \"tables::w2mpro-w3mpro\",\n", - " 'mode': 'markers',\n", - " 'type': 'scatter', \n", - " 'marker': {'size': 4}}\n", - " trace_data=[trace1]\n", - " \n", - " layout_s = {'title': 'Color-Color', \n", - " 'xaxis': {'title': 'w1mpro-w2mpro (mag)'}, 'yaxis': {'title': 'w2mpro-w3mpro (mag)'}} \n", - " fc.show_chart(group_id=viewer_id, layout=layout_s, data=trace_data )\n", - " \n", - "# Add second chart - heatmap (plot.ly direct plot) \n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro-w2mpro\",\n", + " \"y\": \"tables::w2mpro-w3mpro\",\n", + " \"mode\": \"markers\",\n", + " \"type\": \"scatter\",\n", + " \"marker\": {\"size\": 4},\n", + " }\n", + " trace_data = [trace1]\n", + "\n", + " layout_s = {\n", + " \"title\": \"Color-Color\",\n", + " \"xaxis\": {\"title\": \"w1mpro-w2mpro (mag)\"},\n", + " \"yaxis\": {\"title\": \"w2mpro-w3mpro (mag)\"},\n", + " }\n", + " fc.show_chart(group_id=viewer_id, layout=layout_s, data=trace_data)\n", + "\n", + "# Add second chart - heatmap (plot.ly direct plot)\n", "# in cell 2, 0, 2, 3\n", - "viewer_id = 'heatMapContainer'\n", - "r = fc.add_cell(2, 0, 2, 3,'xyPlots', viewer_id)\n", + "viewer_id = \"heatMapContainer\"\n", + "r = fc.add_cell(2, 0, 2, 3, \"xyPlots\", viewer_id)\n", "print(r)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " dataHM = [\n", - " {\n", - " 'type': 'fireflyHeatmap',\n", - " 'name': 'w1 vs. w2',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w2mpro\",\n", - " 'colorscale': 'Blues'\n", - " },\n", - " {\n", - " 'type': 'fireflyHeatmap',\n", - " 'name': 'w1 vs. w3',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w3mpro\",\n", - " 'colorscale': 'Reds',\n", - " 'reversescale': True\n", - " },\n", - " {\n", - " 'type': 'fireflyHeatmap',\n", - " 'name': 'w1 vs. w4',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w4mpro\",\n", - " 'colorscale': 'Greens'\n", - " }]\n", + " {\n", + " \"type\": \"fireflyHeatmap\",\n", + " \"name\": \"w1 vs. w2\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w2mpro\",\n", + " \"colorscale\": \"Blues\",\n", + " },\n", + " {\n", + " \"type\": \"fireflyHeatmap\",\n", + " \"name\": \"w1 vs. w3\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w3mpro\",\n", + " \"colorscale\": \"Reds\",\n", + " \"reversescale\": True,\n", + " },\n", + " {\n", + " \"type\": \"fireflyHeatmap\",\n", + " \"name\": \"w1 vs. w4\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w4mpro\",\n", + " \"colorscale\": \"Greens\",\n", + " },\n", + " ]\n", "\n", - " layout_hm = {'title': 'Magnitude-magnitude densities', \n", - " 'xaxis': {'title': 'w1 photometry (mag)'}, 'yaxis': {'title': ''},\n", - " 'firefly': {'xaxis': {'min': 5, 'max': 20}, 'yaxis': {'min': 4, 'max': 18}}} \n", + " layout_hm = {\n", + " \"title\": \"Magnitude-magnitude densities\",\n", + " \"xaxis\": {\"title\": \"w1 photometry (mag)\"},\n", + " \"yaxis\": {\"title\": \"\"},\n", + " \"firefly\": {\"xaxis\": {\"min\": 5, \"max\": 20}, \"yaxis\": {\"min\": 4, \"max\": 18}},\n", + " }\n", "\n", - " fc.show_chart(group_id=viewer_id, layout=layout_hm, data=dataHM) \n", - " \n", - "# Add third chart - histogram (plot.ly direct plot) \n", + " fc.show_chart(group_id=viewer_id, layout=layout_hm, data=dataHM)\n", + "\n", + "# Add third chart - histogram (plot.ly direct plot)\n", "# in cell 2, 2, 2, 3\n", - "viewer_id = 'histContainer'\n", - "r = fc.add_cell(2, 2, 2, 3, 'xyPlots', viewer_id)\n", + "viewer_id = \"histContainer\"\n", + "r = fc.add_cell(2, 2, 2, 3, \"xyPlots\", viewer_id)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " dataH = [\n", " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'w1mpro', \n", - " 'marker': {'color': 'rgba(153, 51, 153, 0.8)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 30,\n", - " 'columnOrExpr': 'w1mpro'\n", - " }\n", + " \"type\": \"fireflyHistogram\",\n", + " \"name\": \"w1mpro\",\n", + " \"marker\": {\"color\": \"rgba(153, 51, 153, 0.8)\"},\n", + " \"firefly\": {\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"options\": {\n", + " \"algorithm\": \"fixedSizeBins\",\n", + " \"fixedBinSizeSelection\": \"numBins\",\n", + " \"numBins\": 30,\n", + " \"columnOrExpr\": \"w1mpro\",\n", + " },\n", " },\n", " },\n", " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'w2mpro', \n", - " 'marker': {'color': 'rgba(102, 153, 0, 0.7)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 40,\n", - " 'columnOrExpr': 'w2mpro' \n", - " }\n", - " }\n", - " }\n", + " \"type\": \"fireflyHistogram\",\n", + " \"name\": \"w2mpro\",\n", + " \"marker\": {\"color\": \"rgba(102, 153, 0, 0.7)\"},\n", + " \"firefly\": {\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"options\": {\n", + " \"algorithm\": \"fixedSizeBins\",\n", + " \"fixedBinSizeSelection\": \"numBins\",\n", + " \"numBins\": 40,\n", + " \"columnOrExpr\": \"w2mpro\",\n", + " },\n", + " },\n", + " },\n", " ]\n", "\n", - " layout_hist = {'title': 'Photometry histogram',\n", - " 'xaxis': {'title': 'photometry (mag)'}, 'yaxis': {'title': ''}} \n", - " result = fc.show_chart(group_id=viewer_id, layout=layout_hist, data=dataH ) \n", - " \n", + " layout_hist = {\n", + " \"title\": \"Photometry histogram\",\n", + " \"xaxis\": {\"title\": \"photometry (mag)\"},\n", + " \"yaxis\": {\"title\": \"\"},\n", + " }\n", + " result = fc.show_chart(group_id=viewer_id, layout=layout_hist, data=dataH)\n", + "\n", "# Add fourth chart - 3D plot (plot.ly direct plot)\n", - "# in cell 2, 4, 2, 3, \n", - "viewer_id = '3dChartContainer'\n", - "r = fc.add_cell(2, 4, 2, 3, 'xyPlots', viewer_id)\n", + "# in cell 2, 4, 2, 3,\n", + "viewer_id = \"3dChartContainer\"\n", + "r = fc.add_cell(2, 4, 2, 3, \"xyPlots\", viewer_id)\n", "\n", - "if r['success']:\n", + "if r[\"success\"]:\n", " data3d = [\n", " {\n", - " 'type': 'scatter3d',\n", - " 'name': 'w1-w2-w3',\n", - " 'tbl_id': 'wiseCatTbl',\n", - " 'x': \"tables::w1mpro\",\n", - " 'y': \"tables::w2mpro\",\n", - " 'z': \"tables::w3mpro\",\n", - " 'mode' : 'markers',\n", - " 'marker' : {\n", - " 'size': 3,\n", - " 'color': 'rgba(127, 127, 127, 1)',\n", - " 'line': {\n", - " 'color': 'rgba(127, 127, 127, 0.5)',\n", - " 'width': 1}\n", + " \"type\": \"scatter3d\",\n", + " \"name\": \"w1-w2-w3\",\n", + " \"tbl_id\": \"wiseCatTbl\",\n", + " \"x\": \"tables::w1mpro\",\n", + " \"y\": \"tables::w2mpro\",\n", + " \"z\": \"tables::w3mpro\",\n", + " \"mode\": \"markers\",\n", + " \"marker\": {\n", + " \"size\": 3,\n", + " \"color\": \"rgba(127, 127, 127, 1)\",\n", + " \"line\": {\"color\": \"rgba(127, 127, 127, 0.5)\", \"width\": 1},\n", " },\n", - " 'hoverinfo': 'x+y+z'\n", - " }]\n", + " \"hoverinfo\": \"x+y+z\",\n", + " }\n", + " ]\n", "\n", - " fsize = {'size': 11}\n", + " fsize = {\"size\": 11}\n", " layout3d = {\n", - " 'title': 'Photometry in band 1, 2, 3',\n", - " 'scene':{\n", - " 'xaxis': {\n", - " 'title': 'w1 (mag)',\n", - " 'titlefont': fsize\n", - " },\n", - " 'yaxis': {\n", - " 'title': 'w2 (mag)',\n", - " 'titlefont': fsize\n", - " },\n", - " 'zaxis': {\n", - " 'title': 'w3 (mag)',\n", - " 'titlefont': fsize\n", - " }\n", - " }\n", + " \"title\": \"Photometry in band 1, 2, 3\",\n", + " \"scene\": {\n", + " \"xaxis\": {\"title\": \"w1 (mag)\", \"titlefont\": fsize},\n", + " \"yaxis\": {\"title\": \"w2 (mag)\", \"titlefont\": fsize},\n", + " \"zaxis\": {\"title\": \"w3 (mag)\", \"titlefont\": fsize},\n", + " },\n", " }\n", - " \n", - " fc.show_chart(group_id=viewer_id, is_3d=True, layout=layout3d, data=data3d ) \n", - " \n", + "\n", + " fc.show_chart(group_id=viewer_id, is_3d=True, layout=layout3d, data=data3d)\n", + "\n", "# Add a three color fits\n", "# in cell 0, 2, 2, 2\n", - "viewer_id = '3C'\n", - "r = fc.add_cell(0, 2, 2, 2, 'images', viewer_id)\n", - "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", - "if r['success']:\n", - " threeC= [\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '1',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '2',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '3',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " }] \n", + "viewer_id = \"3C\"\n", + "r = fc.add_cell(0, 2, 2, 2, \"images\", viewer_id)\n", + "rv = \"92,-2,92,8,NaN,2,44,25,600,120\"\n", + "if r[\"success\"]:\n", + " threeC = [\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"1\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"2\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"3\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " ]\n", " fc.show_fits_3color(threeC, viewer_id=viewer_id)" ] }, @@ -315,7 +323,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.change_triview_layout( FireflyClient.BIVIEW_IChCov_T)" + "fc.change_triview_layout(FireflyClient.BIVIEW_IChCov_T)" ] }, { @@ -324,38 +332,39 @@ "metadata": {}, "outputs": [], "source": [ - "rv = '92,-2,92,8,NaN,2,44,25,600,120'\n", - "threeC= [\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '1',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '2',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " },\n", - " {\n", - " 'Type' : 'SERVICE',\n", - " 'Service' : 'WISE',\n", - " 'Title' : '3 color',\n", - " 'SurveyKey' : 'Atlas',\n", - " 'SurveyKeyBand': '3',\n", - " 'WorldPt' : target,\n", - " 'RangeValues': rv,\n", - " 'SizeInDeg' : '1'\n", - " }] \n", + "rv = \"92,-2,92,8,NaN,2,44,25,600,120\"\n", + "threeC = [\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"1\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"2\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + " {\n", + " \"Type\": \"SERVICE\",\n", + " \"Service\": \"WISE\",\n", + " \"Title\": \"3 color\",\n", + " \"SurveyKey\": \"Atlas\",\n", + " \"SurveyKeyBand\": \"3\",\n", + " \"WorldPt\": target,\n", + " \"RangeValues\": rv,\n", + " \"SizeInDeg\": \"1\",\n", + " },\n", + "]\n", "fc.show_fits_3color(threeC)" ] }, diff --git a/examples/demo-advanced-steps.ipynb b/examples/demo-advanced-steps.ipynb index ac27066..be645eb 100644 --- a/examples/demo-advanced-steps.ipynb +++ b/examples/demo-advanced-steps.ipynb @@ -25,9 +25,10 @@ "source": [ "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", "using_lab = True\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -37,7 +38,7 @@ "outputs": [], "source": [ "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "fc.change_triview_layout( FireflyClient.BIVIEW_T_IChCov)" + "fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov)" ] }, { @@ -78,13 +79,23 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl\",\n", - " timeout=120, cache=True)\n", - "meta_info = {'datasetInfoConverterId': 'SimpleMoving','positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000',\n", - " 'datasource': 'image_url'}\n", - "fc.show_table(fc.upload_file(tbl_name), tbl_id='movingtbl', title='A moving object table', page_size=15, meta=meta_info)\n", - " " + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", + "meta_info = {\n", + " \"datasetInfoConverterId\": \"SimpleMoving\",\n", + " \"positionCoordColumns\": \"ra_obj;dec_obj;EQ_J2000\",\n", + " \"datasource\": \"image_url\",\n", + "}\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " tbl_id=\"movingtbl\",\n", + " title=\"A moving object table\",\n", + " page_size=15,\n", + " meta=meta_info,\n", + ")" ] }, { @@ -100,9 +111,17 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl',\n", - " timeout=120, cache=True)\n", - "fc.show_table(fc.upload_file(tbl_name), tbl_id='tbl_chart', title='table for xyplot and histogram', page_size=15)" + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " tbl_id=\"tbl_chart\",\n", + " title=\"table for xyplot and histogram\",\n", + " page_size=15,\n", + ")" ] }, { @@ -118,10 +137,18 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl\", \n", - " timeout=120, cache=True)\n", - "meta_info = {'datasource': 'DP'}\n", - "fc.show_table(fc.upload_file(tbl_name), title='A table of simple images', page_size=15, meta=meta_info)" + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", + "meta_info = {\"datasource\": \"DP\"}\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " title=\"A table of simple images\",\n", + " page_size=15,\n", + " meta=meta_info,\n", + ")" ] }, { @@ -137,11 +164,19 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_name = astropy.utils.data.download_file(\"http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl\", \n", - " timeout=120, cache=True)\n", + "tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", "\n", - "meta_info = {'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', 'datasource': 'FITS'}\n", - "fc.show_table(fc.upload_file(tbl_name), title='A table of m31 images', page_size=15, meta=meta_info)" + "meta_info = {\"positionCoordColumns\": \"ra_obj;dec_obj;EQ_J2000\", \"datasource\": \"FITS\"}\n", + "fc.show_table(\n", + " fc.upload_file(tbl_name),\n", + " title=\"A table of m31 images\",\n", + " page_size=15,\n", + " meta=meta_info,\n", + ")" ] }, { @@ -174,11 +209,11 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "image_name = astropy.utils.data.download_file('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' +\n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", - "fc.show_fits(file_on_server=fc.upload_file(image_name), title='WISE Cutout')\n", - " " + "image_name = astropy.utils.data.download_file(\n", + " \"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix\"\n", + ")\n", + "fc.show_fits(file_on_server=fc.upload_file(image_name), title=\"WISE Cutout\")" ] }, { @@ -194,20 +229,38 @@ "metadata": {}, "outputs": [], "source": [ - "\n", - "fc.show_fits(plot_id='m49025b_143_2', OverlayPosition='330.347003;-2.774482;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49025b143-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits')\n", - "fc.show_fits(plot_id='m49273b_134_2', OverlayPosition='333.539702;-0.779310;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49273b134-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits')\n", - "fc.show_fits(plot_id='m49277b_135_1', OverlayPosition='333.589054;-0.747251;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49277b135-w1', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits')\n", - "fc.show_fits(plot_id='m49289b_134_2', OverlayPosition='333.736578;-0.651222;EQ_J2000',\n", - " ZoomType='TO_WIDTH_HEIGHT', Title='49289b134-w2', plotGroupId='movingGroup',\n", - " URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits')\n", - " " + "fc.show_fits(\n", + " plot_id=\"m49025b_143_2\",\n", + " OverlayPosition=\"330.347003;-2.774482;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49025b143-w2\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits\",\n", + ")\n", + "fc.show_fits(\n", + " plot_id=\"m49273b_134_2\",\n", + " OverlayPosition=\"333.539702;-0.779310;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49273b134-w2\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits\",\n", + ")\n", + "fc.show_fits(\n", + " plot_id=\"m49277b_135_1\",\n", + " OverlayPosition=\"333.589054;-0.747251;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49277b135-w1\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits\",\n", + ")\n", + "fc.show_fits(\n", + " plot_id=\"m49289b_134_2\",\n", + " OverlayPosition=\"333.736578;-0.651222;EQ_J2000\",\n", + " ZoomType=\"TO_WIDTH_HEIGHT\",\n", + " Title=\"49289b134-w2\",\n", + " plotGroupId=\"movingGroup\",\n", + " URL=\"http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits\",\n", + ")" ] }, { @@ -226,19 +279,22 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "trace1 = {\n", - " 'tbl_id': 'tbl_chart',\n", - " 'x': \"tables::ra1\",\n", - " 'y': \"tables::dec1\",\n", - " 'mode': 'markers',\n", - " 'type': 'scatter', \n", - " 'marker': {'size': 4}}\n", - "trace_data=[trace1]\n", - " \n", - "layout_s = {'title': 'Coordinates', \n", - " 'xaxis': {'title': 'ra1 (deg)'}, 'yaxis': {'title': 'dec1 (deg)'}} \n", - "fc.show_chart( layout=layout_s, data=trace_data ) " + " \"tbl_id\": \"tbl_chart\",\n", + " \"x\": \"tables::ra1\",\n", + " \"y\": \"tables::dec1\",\n", + " \"mode\": \"markers\",\n", + " \"type\": \"scatter\",\n", + " \"marker\": {\"size\": 4},\n", + "}\n", + "trace_data = [trace1]\n", + "\n", + "layout_s = {\n", + " \"title\": \"Coordinates\",\n", + " \"xaxis\": {\"title\": \"ra1 (deg)\"},\n", + " \"yaxis\": {\"title\": \"dec1 (deg)\"},\n", + "}\n", + "fc.show_chart(layout=layout_s, data=trace_data)" ] }, { @@ -256,27 +312,29 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "histData = [\n", - " {\n", - " 'type': 'fireflyHistogram',\n", - " 'name': 'magzp', \n", - " 'marker': {'color': 'rgba(153, 51, 153, 0.8)'},\n", - " 'firefly': {\n", - " 'tbl_id': 'tbl_chart',\n", - " 'options': {\n", - " 'algorithm': 'fixedSizeBins',\n", - " 'fixedBinSizeSelection': 'numBins',\n", - " 'numBins': 30,\n", - " 'columnOrExpr': 'magzp'\n", - " }\n", + " {\n", + " \"type\": \"fireflyHistogram\",\n", + " \"name\": \"magzp\",\n", + " \"marker\": {\"color\": \"rgba(153, 51, 153, 0.8)\"},\n", + " \"firefly\": {\n", + " \"tbl_id\": \"tbl_chart\",\n", + " \"options\": {\n", + " \"algorithm\": \"fixedSizeBins\",\n", + " \"fixedBinSizeSelection\": \"numBins\",\n", + " \"numBins\": 30,\n", + " \"columnOrExpr\": \"magzp\",\n", " },\n", - " }\n", - " ]\n", + " },\n", + " }\n", + "]\n", "\n", - "layout_hist = {'title': 'Magnitude Zeropoints',\n", - " 'xaxis': {'title': 'magzp'}, 'yaxis': {'title': ''}} \n", - "result = fc.show_chart(layout=layout_hist, data=histData ) " + "layout_hist = {\n", + " \"title\": \"Magnitude Zeropoints\",\n", + " \"xaxis\": {\"title\": \"magzp\"},\n", + " \"yaxis\": {\"title\": \"\"},\n", + "}\n", + "result = fc.show_chart(layout=layout_hist, data=histData)" ] }, { @@ -311,8 +369,11 @@ "metadata": {}, "outputs": [], "source": [ - "img_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits', \n", - " timeout=120, cache=True)\n", + "img_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", "fc.show_fits(file_on_server=fc.upload_file(img_name))" ] }, @@ -322,7 +383,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(plot_id='zzz', file_on_server=fc.upload_file(img_name))" + "fc.show_fits(plot_id=\"zzz\", file_on_server=fc.upload_file(img_name))" ] }, { @@ -338,9 +399,17 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(plot_id='xxq', Service='TWOMASS', Title='2mass from service', ZoomType='LEVEL',\n", - " initZoomLevel=2, SurveyKey='asky', SurveyKeyBand='k',\n", - " WorldPt='10.68479;41.26906;EQ_J2000', SizeInDeg='.12')" + "fc.show_fits(\n", + " plot_id=\"xxq\",\n", + " Service=\"TWOMASS\",\n", + " Title=\"2mass from service\",\n", + " ZoomType=\"LEVEL\",\n", + " initZoomLevel=2,\n", + " SurveyKey=\"asky\",\n", + " SurveyKeyBand=\"k\",\n", + " WorldPt=\"10.68479;41.26906;EQ_J2000\",\n", + " SizeInDeg=\".12\",\n", + ")" ] } ], diff --git a/examples/demo-advanced-table-images.ipynb b/examples/demo-advanced-table-images.ipynb index dbcb27f..85b1fc1 100644 --- a/examples/demo-advanced-table-images.ipynb +++ b/examples/demo-advanced-table-images.ipynb @@ -31,9 +31,10 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -115,8 +116,8 @@ "metadata": {}, "outputs": [], "source": [ - "# first table in cell 'main' \n", - "fs.load_moving_table(0,0,4,2, fc)" + "# first table in cell 'main'\n", + "fs.load_moving_table(0, 0, 4, 2, fc)" ] }, { @@ -125,7 +126,7 @@ "metadata": {}, "outputs": [], "source": [ - "# add table in cell 'main' for chart and histogram \n", + "# add table in cell 'main' for chart and histogram\n", "fs.add_table_for_chart(fc)" ] }, @@ -163,7 +164,7 @@ "outputs": [], "source": [ "# show cell containing the image from the active table with datasource column in cell 'main'\n", - "fs.load_image_metadata(2, 0, 4, 2, fc, 'image-meta')" + "fs.load_image_metadata(2, 0, 4, 2, fc, \"image-meta\")" ] }, { @@ -173,7 +174,7 @@ "outputs": [], "source": [ "# show cell containing FITS in cell 'wise-cutout'\n", - "fs.load_image(0, 4, 2, 2, fc, 'wise-cutout') # load an image" + "fs.load_image(0, 4, 2, 2, fc, \"wise-cutout\") # load an image" ] }, { @@ -183,7 +184,7 @@ "outputs": [], "source": [ "# show cell with 4 FITS of moving objects in cell 'movingStff'\n", - "fs.load_moving(2, 4, 2, 2, fc, 'movingStuff')" + "fs.load_moving(2, 4, 2, 2, fc, \"movingStuff\")" ] }, { @@ -200,7 +201,7 @@ "outputs": [], "source": [ "# show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main'\n", - "fs.load_xy(4, 0, 2, 3, fc, 'chart-cell-xy')" + "fs.load_xy(4, 0, 2, 3, fc, \"chart-cell-xy\")" ] }, { @@ -227,7 +228,7 @@ "outputs": [], "source": [ "# show cell containing coverage image associated with the active table in cell 'main'\n", - "fs.load_coverage_image(4, 4, 3, 3, fc, 'image-coverage')" + "fs.load_coverage_image(4, 4, 3, 3, fc, \"image-coverage\")" ] }, { @@ -246,7 +247,7 @@ "metadata": {}, "outputs": [], "source": [ - "# show second image in random location. This image is located in the same cell as the previous one \n", + "# show second image in random location. This image is located in the same cell as the previous one\n", "fs.load_second_image_in_random(fc)" ] } diff --git a/examples/demo-advanced-tables-images-upload.ipynb b/examples/demo-advanced-tables-images-upload.ipynb index 1825c59..9b0fee8 100644 --- a/examples/demo-advanced-tables-images-upload.ipynb +++ b/examples/demo-advanced-tables-images-upload.ipynb @@ -32,9 +32,10 @@ "source": [ "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -105,8 +106,8 @@ "metadata": {}, "outputs": [], "source": [ - "# first table in cell 'main' \n", - "fs.load_moving_table(0,0,4,2, fc)" + "# first table in cell 'main'\n", + "fs.load_moving_table(0, 0, 4, 2, fc)" ] }, { @@ -115,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "# add table in cell 'main' for chart and histogram \n", + "# add table in cell 'main' for chart and histogram\n", "fs.add_table_for_chart(fc)" ] }, @@ -145,8 +146,8 @@ "metadata": {}, "outputs": [], "source": [ - "f= '/Users/roby/fits/2mass-m31-2412rows.tbl'\n", - "file= fc.upload_file(f);" + "f = \"/Users/roby/fits/2mass-m31-2412rows.tbl\"\n", + "file = fc.upload_file(f);" ] }, { @@ -155,7 +156,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_table(file)\n" + "fc.show_table(file)" ] }, { @@ -164,7 +165,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_table('${cache-dir}/upload_2482826742890803252.fits')\n" + "fc.show_table(\"${cache-dir}/upload_2482826742890803252.fits\")" ] }, { @@ -181,7 +182,7 @@ "outputs": [], "source": [ "# show cell containing the image from the active table with datasource column in cell 'main'\n", - "fs.load_image_metadata(2, 0, 4, 2, fc, 'image-meta')" + "fs.load_image_metadata(2, 0, 4, 2, fc, \"image-meta\")" ] }, { @@ -191,7 +192,7 @@ "outputs": [], "source": [ "# show cell containing FITS in cell 'wise-cutout'\n", - "fs.load_image(0, 4, 2, 2, fc, 'wise-cutout') # load an image" + "fs.load_image(0, 4, 2, 2, fc, \"wise-cutout\") # load an image" ] }, { @@ -201,7 +202,7 @@ "outputs": [], "source": [ "# show cell with 4 FITS of moving objects in cell 'movingStff'\n", - "fs.load_moving(2, 4, 2, 2, fc, 'movingStuff')" + "fs.load_moving(2, 4, 2, 2, fc, \"movingStuff\")" ] }, { @@ -218,7 +219,7 @@ "outputs": [], "source": [ "# show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main'\n", - "fs.load_xy(4, 0, 2, 3, fc, 'chart-cell-xy')" + "fs.load_xy(4, 0, 2, 3, fc, \"chart-cell-xy\")" ] }, { @@ -245,7 +246,7 @@ "outputs": [], "source": [ "# show cell containing coverage image associated with the active table in cell 'main'\n", - "fs.load_coverage_image(4, 4, 3, 3, fc, 'image-coverage')" + "fs.load_coverage_image(4, 4, 3, 3, fc, \"image-coverage\")" ] }, { @@ -264,7 +265,7 @@ "metadata": {}, "outputs": [], "source": [ - "# show second image in random location. This image is located in the same cell as the previous one \n", + "# show second image in random location. This image is located in the same cell as the previous one\n", "fs.load_second_image_in_random(fc)" ] } diff --git a/examples/demo-basic.ipynb b/examples/demo-basic.ipynb index 343fe79..7bbfaf8 100644 --- a/examples/demo-basic.ipynb +++ b/examples/demo-basic.ipynb @@ -38,10 +38,11 @@ "source": [ "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", "using_lab = False\n", - "#url = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "# url = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -94,8 +95,10 @@ "metadata": {}, "outputs": [], "source": [ - "image_url = ('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + \n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", + "image_url = (\n", + " \"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix\"\n", + ")\n", "filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120)" ] }, @@ -112,9 +115,11 @@ "metadata": {}, "outputs": [], "source": [ - "table_url = (\"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&\" +\n", - " \"QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),\" +\n", - " \"CIRCLE('J2000',70.0,20.0,0.1))=1\")\n", + "table_url = (\n", + " \"http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&\"\n", + " + \"QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec),\"\n", + " + \"CIRCLE('J2000',70.0,20.0,0.1))=1\"\n", + ")\n", "tablename = astropy.utils.data.download_file(table_url, timeout=120, cache=True)" ] }, @@ -190,7 +195,7 @@ "outputs": [], "source": [ "imval = fc.upload_file(filename)\n", - "status = fc.show_fits(file_on_server=imval, plot_id=\"wise-cutout\", title='WISE Cutout')" + "status = fc.show_fits(file_on_server=imval, plot_id=\"wise-cutout\", title=\"WISE Cutout\")" ] }, { @@ -206,9 +211,12 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.show_fits(file_on_server=None, plot_id=\"wise-fullimage\", \n", - " URL='http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + \n", - " '/149/02206a149-w1-int-1b.fits')" + "status = fc.show_fits(\n", + " file_on_server=None,\n", + " plot_id=\"wise-fullimage\",\n", + " URL=\"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits\",\n", + ")" ] }, { @@ -224,8 +232,8 @@ "metadata": {}, "outputs": [], "source": [ - "file= fc.upload_file(tablename)\n", - "status = fc.show_table(file, tbl_id='tablemass', title='My 2MASS Catalog', page_size=50)" + "file = fc.upload_file(tablename)\n", + "status = fc.show_table(file, tbl_id=\"tablemass\", title=\"My 2MASS Catalog\", page_size=50)" ] }, { @@ -241,7 +249,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.show_xyplot(tbl_id='tablemass', xCol='j_m', yCol='h_m-k_m')" + "status = fc.show_xyplot(tbl_id=\"tablemass\", xCol=\"j_m\", yCol=\"h_m-k_m\")" ] }, { @@ -257,7 +265,7 @@ "metadata": {}, "outputs": [], "source": [ - "# trace0 = {'tbl_id': 'tablemass', 'x': \"tables::j_m\", 'y': \"tables::h_m-k_m\", \n", + "# trace0 = {'tbl_id': 'tablemass', 'x': \"tables::j_m\", 'y': \"tables::h_m-k_m\",\n", "# 'type' : 'scatter', 'mode': 'markers'}\n", "# status = fc.show_chart(data=[trace0])" ] @@ -275,7 +283,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_zoom('wise-fullimage', 2)" + "status = fc.set_zoom(\"wise-fullimage\", 2)" ] }, { @@ -291,7 +299,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_pan('wise-fullimage', x=70, y=20, coord='J2000')" + "status = fc.set_pan(\"wise-fullimage\", x=70, y=20, coord=\"J2000\")" ] }, { @@ -307,7 +315,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_stretch('wise-fullimage', stype='zscale', algorithm='linear')" + "status = fc.set_stretch(\"wise-fullimage\", stype=\"zscale\", algorithm=\"linear\")" ] }, { @@ -323,7 +331,7 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.set_color('wise-fullimage', colormap_id=6, bias=0.6, contrast=1.5)" + "status = fc.set_color(\"wise-fullimage\", colormap_id=6, bias=0.6, contrast=1.5)" ] }, { @@ -339,10 +347,13 @@ "metadata": {}, "outputs": [], "source": [ - "my_regions= ['image;polygon 125 25 160 195 150 150 #color=cyan',\n", - " 'icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}']\n", - "status = fc.add_region_data(region_data=my_regions, region_layer_id='layer1',\n", - " plot_id='wise-cutout')" + "my_regions = [\n", + " \"image;polygon 125 25 160 195 150 150 #color=cyan\",\n", + " \"icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}\",\n", + "]\n", + "status = fc.add_region_data(\n", + " region_data=my_regions, region_layer_id=\"layer1\", plot_id=\"wise-cutout\"\n", + ")" ] }, { @@ -358,7 +369,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.remove_region_data(region_data=my_regions[1], region_layer_id='layer1')" + "fc.remove_region_data(region_data=my_regions[1], region_layer_id=\"layer1\")" ] }, { diff --git a/examples/demo-lsst-footprint.ipynb b/examples/demo-lsst-footprint.ipynb index 2b63fab..c1c5c1b 100644 --- a/examples/demo-lsst-footprint.ipynb +++ b/examples/demo-lsst-footprint.ipynb @@ -38,9 +38,10 @@ "source": [ "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -86,7 +87,9 @@ "metadata": {}, "outputs": [], "source": [ - "image_url = 'http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits'\n", + "image_url = (\n", + " \"http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits\"\n", + ")\n", "filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120)\n", "imval = fc.upload_file(filename)" ] @@ -104,8 +107,10 @@ "metadata": {}, "outputs": [], "source": [ - "plotid = 'footprinttest'\n", - "status = fc.show_fits(file_on_server=imval, plot_id=plotid, title='footprints HSC R-band')" + "plotid = \"footprinttest\"\n", + "status = fc.show_fits(\n", + " file_on_server=imval, plot_id=plotid, title=\"footprints HSC R-band\"\n", + ")" ] }, { @@ -128,7 +133,7 @@ "metadata": {}, "outputs": [], "source": [ - "table_url = 'http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml'\n", + "table_url = \"http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml\"\n", "footprint_table = astropy.utils.data.download_file(table_url, cache=True, timeout=120)\n", "tableval = fc.upload_file(footprint_table)" ] @@ -146,13 +151,16 @@ "metadata": {}, "outputs": [], "source": [ - "status = fc.overlay_footprints(tableval, title='footprints HSC R-band',\n", - " footprint_layer_id='footprint_layer_1', \n", - " plot_id=plotid, \n", - " highlightColor='yellow', \n", - " selectColor='cyan', \n", - " style='fill', \n", - " color='rgba(74,144,226,0.30)')" + "status = fc.overlay_footprints(\n", + " tableval,\n", + " title=\"footprints HSC R-band\",\n", + " footprint_layer_id=\"footprint_layer_1\",\n", + " plot_id=plotid,\n", + " highlightColor=\"yellow\",\n", + " selectColor=\"cyan\",\n", + " style=\"fill\",\n", + " color=\"rgba(74,144,226,0.30)\",\n", + ")" ] } ], diff --git a/examples/demo-region.ipynb b/examples/demo-region.ipynb index 25d3679..f87f146 100644 --- a/examples/demo-region.ipynb +++ b/examples/demo-region.ipynb @@ -38,9 +38,10 @@ "source": [ "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -95,8 +96,10 @@ "metadata": {}, "outputs": [], "source": [ - "image_url = ('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + \n", - " '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix')\n", + "image_url = (\n", + " \"http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a\"\n", + " + \"/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix\"\n", + ")\n", "filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120)" ] }, @@ -153,7 +156,9 @@ "outputs": [], "source": [ "imval = fc.upload_file(filename)\n", - "status = fc.show_fits(file_on_server=imval, plot_id=\"region test\", title='text region test')" + "status = fc.show_fits(\n", + " file_on_server=imval, plot_id=\"region test\", title=\"text region test\"\n", + ")" ] }, { @@ -169,14 +174,16 @@ "metadata": {}, "outputs": [], "source": [ - "text_regions= [\n", + "text_regions = [\n", " 'image;text 100 150 # color=pink text={text angle is 30 deg } edit=0 highlite=0 font=\"Times 16 bold normal\" textangle=30',\n", " 'image;text 100 25 # color=pink text={text angle is -20 deg } font=\"Times 16 bold italic\" textangle=-20',\n", - " 'circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan',\n", - " 'image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}',\n", - " 'circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \" and \\' in it}']\n", - "status = fc.add_region_data(region_data=text_regions, region_layer_id='layerTextRegion',\n", - " plot_id='region test')" + " \"circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan\",\n", + " \"image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}\",\n", + " \"circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \\\" and ' in it}\",\n", + "]\n", + "status = fc.add_region_data(\n", + " region_data=text_regions, region_layer_id=\"layerTextRegion\", plot_id=\"region test\"\n", + ")" ] } ], diff --git a/examples/demo-show-image.ipynb b/examples/demo-show-image.ipynb index 9c42e65..3348fe6 100644 --- a/examples/demo-show-image.ipynb +++ b/examples/demo-show-image.ipynb @@ -7,9 +7,10 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", + "\n", "using_lab = False\n", - "url = 'http://127.0.0.1:8080/firefly'\n", - "#FireflyClient._debug= True" + "url = \"http://127.0.0.1:8080/firefly\"\n", + "# FireflyClient._debug= True" ] }, { @@ -18,8 +19,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)\n", - "\n" + "fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url)" ] }, { @@ -28,7 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(URL='http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits')\n" + "fc.show_fits(URL=\"http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits\")" ] } ], diff --git a/examples/firefly_slate_demo.py b/examples/firefly_slate_demo.py index 771d003..0816983 100644 --- a/examples/firefly_slate_demo.py +++ b/examples/firefly_slate_demo.py @@ -6,135 +6,213 @@ def download_from(url): def load_moving_table(row, col, width, height, fc, cell_id=None): - moving_tbl_name = download_from("http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl") - - r = fc.add_cell(row, col, width, height, 'tables', cell_id) - meta_info = {'datasetInfoConverterId': 'SimpleMoving', - 'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', - 'datasource': 'image_url'} + moving_tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl" + ) + + r = fc.add_cell(row, col, width, height, "tables", cell_id) + meta_info = { + "datasetInfoConverterId": "SimpleMoving", + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "image_url", + } tbl_name = fc.upload_file(moving_tbl_name) - if r['success']: - fc.show_table(tbl_name, tbl_id='movingtbl', title='A moving object table', - page_size=15, meta=meta_info) + if r["success"]: + fc.show_table( + tbl_name, + tbl_id="movingtbl", + title="A moving object table", + page_size=15, + meta=meta_info, + ) def add_simple_m31_image_table(fc): - tbl_name = download_from("http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl") - - meta_info = {'positionCoordColumns': 'ra_obj;dec_obj;EQ_J2000', - 'datasource': 'FITS'} - fc.show_table(fc.upload_file(tbl_name), title='A table of m31 images', page_size=15, meta=meta_info) + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl" + ) + + meta_info = { + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "FITS", + } + fc.show_table( + fc.upload_file(tbl_name), + title="A table of m31 images", + page_size=15, + meta=meta_info, + ) def add_simple_image_table(fc): - tbl_name = download_from("http://web.ipac.caltech.edu/staff/roby/demo/test-table4.tbl") + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table4.tbl" + ) - meta_info = {'datasource': 'FITS'} - fc.show_table(fc.upload_file(tbl_name), title='A table of simple images', page_size=15, meta=meta_info) + meta_info = {"datasource": "FITS"} + fc.show_table( + fc.upload_file(tbl_name), + title="A table of simple images", + page_size=15, + meta=meta_info, + ) def add_table_for_chart(fc): - tbl_name = download_from('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl') + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl" + ) - fc.show_table(fc.upload_file(tbl_name), tbl_id='tbl_chart', title='table for xyplot and histogram', page_size=15) + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="tbl_chart", + title="table for xyplot and histogram", + page_size=15, + ) def load_image(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'images', cell_id) + r = fc.add_cell(row, col, width, height, "images", cell_id) - image_name = download_from('http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a' + - '/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix') + image_name = download_from( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) - if r['success']: - fc.show_fits(file_on_server=fc.upload_file(image_name), viewer_id=r['cell_id'], title='WISE Cutout') + if r["success"]: + fc.show_fits( + file_on_server=fc.upload_file(image_name), + viewer_id=r["cell_id"], + title="WISE Cutout", + ) def load_image_metadata(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'tableImageMeta', cell_id) + r = fc.add_cell(row, col, width, height, "tableImageMeta", cell_id) - if r['success']: - fc.show_image_metadata(viewer_id=r['cell_id']) + if r["success"]: + fc.show_image_metadata(viewer_id=r["cell_id"]) def load_coverage_image(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'coverageImage', cell_id) + r = fc.add_cell(row, col, width, height, "coverageImage", cell_id) - if r['success']: - fc.show_coverage(viewer_id=r['cell_id']) + if r["success"]: + fc.show_coverage(viewer_id=r["cell_id"]) def load_moving(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'images', cell_id) - - if r['success']: - v_id = r['cell_id'] - fc.show_fits(plot_id='m49025b_143_2', viewer_id=v_id, OverlayPosition='330.347003;-2.774482;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49025b143-w2', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits') - fc.show_fits(plot_id='m49273b_134_2', viewer_id=v_id, OverlayPosition='333.539702;-0.779310;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49273b134-w2', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits') - fc.show_fits(plot_id='m49277b_135_1', viewer_id=v_id, OverlayPosition='333.589054;-0.747251;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49277b135-w1', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits') - fc.show_fits(plot_id='m49289b_134_2', viewer_id=v_id, OverlayPosition='333.736578;-0.651222;EQ_J2000', - ZoomType='TO_WIDTH_HEIGHT', Title='49289b134-w2', plotGroupId='movingGroup', - URL='http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits') + r = fc.add_cell(row, col, width, height, "images", cell_id) + + if r["success"]: + v_id = r["cell_id"] + fc.show_fits( + plot_id="m49025b_143_2", + viewer_id=v_id, + OverlayPosition="330.347003;-2.774482;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49025b143-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49273b_134_2", + viewer_id=v_id, + OverlayPosition="333.539702;-0.779310;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49273b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49277b_135_1", + viewer_id=v_id, + OverlayPosition="333.589054;-0.747251;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49277b135-w1", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits", + ) + fc.show_fits( + plot_id="m49289b_134_2", + viewer_id=v_id, + OverlayPosition="333.736578;-0.651222;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49289b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits", + ) def load_xy(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'xyPlots', cell_id) + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) - if r['success']: + if r["success"]: trace1 = { - 'tbl_id': 'tbl_chart', - 'x': "tables::ra1", - 'y': "tables::dec1", - 'mode': 'markers', - 'type': 'scatter', - 'marker': {'size': 4}} - trace_data=[trace1] - - layout_s = {'title': 'Coordinates', - 'xaxis': {'title': 'ra1 (deg)'}, 'yaxis': {'title': 'dec1 (deg)'}} - fc.show_chart(group_id=r['cell_id'], layout=layout_s, data=trace_data ) + "tbl_id": "tbl_chart", + "x": "tables::ra1", + "y": "tables::dec1", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Coordinates", + "xaxis": {"title": "ra1 (deg)"}, + "yaxis": {"title": "dec1 (deg)"}, + } + fc.show_chart(group_id=r["cell_id"], layout=layout_s, data=trace_data) def load_histogram(row, col, width, height, fc, cell_id=None): - r = fc.add_cell(row, col, width, height, 'xyPlots', cell_id) + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) - if r['success']: + if r["success"]: histData = [ { - 'type': 'fireflyHistogram', - 'name': 'magzp', - 'marker': {'color': 'rgba(153, 51, 153, 0.8)'}, - 'firefly': { - 'tbl_id': 'tbl_chart', - 'options': { - 'algorithm': 'fixedSizeBins', - 'fixedBinSizeSelection': 'numBins', - 'numBins': 30, - 'columnOrExpr': 'magzp' - } + "type": "fireflyHistogram", + "name": "magzp", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "tbl_chart", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "magzp", + }, }, } ] - layout_hist = {'title': 'Magnitude Zeropoints', - 'xaxis': {'title': 'magzp'}, 'yaxis': {'title': ''}} - result = fc.show_chart(group_id=r['cell_id'], layout=layout_hist, data=histData ) + layout_hist = { + "title": "Magnitude Zeropoints", + "xaxis": {"title": "magzp"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(group_id=r["cell_id"], layout=layout_hist, data=histData) def load_first_image_in_random(fc): - img_name = download_from('http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits') + img_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits" + ) fc.show_fits(file_on_server=fc.upload_file(img_name)) def load_second_image_in_random(fc): - fc.show_fits(plot_id='xxq', Service='TWOMASS', Title='2mass from service', ZoomType='LEVEL', - initZoomLevel=2, SurveyKey='asky', SurveyKeyBand='k', - WorldPt='10.68479;41.26906;EQ_J2000', SizeInDeg='.12') - + fc.show_fits( + plot_id="xxq", + Service="TWOMASS", + Title="2mass from service", + ZoomType="LEVEL", + initZoomLevel=2, + SurveyKey="asky", + SurveyKeyBand="k", + WorldPt="10.68479;41.26906;EQ_J2000", + SizeInDeg=".12", + ) diff --git a/examples/multi-moc.py b/examples/multi-moc.py index b046570..bb24586 100644 --- a/examples/multi-moc.py +++ b/examples/multi-moc.py @@ -1,15 +1,21 @@ import astropy.utils.data from firefly_client import FireflyClient -fc = FireflyClient.make_client('http://127.0.0.1:8080/firefly', channel_override='moc-channel') -print('firefly url: {}'.format(fc.get_firefly_url())) -mocs = ['galex.fits', 'hershel.fits', 'nicmos.fits'] -meta = {'PREFERRED_HIPS': 'https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color'} +fc = FireflyClient.make_client( + "http://127.0.0.1:8080/firefly", channel_override="moc-channel" +) +print("firefly url: {}".format(fc.get_firefly_url())) +mocs = ["galex.fits", "hershel.fits", "nicmos.fits"] + +meta = {"PREFERRED_HIPS": "https://irsa.ipac.caltech.edu/data/hips/CDS/2MASS/Color"} for m in mocs: - result = input('load {}? (y or n): '.format(m)) - if result == 'y': - downloadName = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/moc/{}'.format(m), - timeout=120, cache=True) - serverFile= fc.upload_file(downloadName) + result = input("load {}? (y or n): ".format(m)) + if result == "y": + downloadName = astropy.utils.data.download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/moc/{}".format(m), + timeout=120, + cache=True, + ) + serverFile = fc.upload_file(downloadName) fc.fetch_table(serverFile, m, title=m, meta=meta) print(m) diff --git a/examples/plot-interface.ipynb b/examples/plot-interface.ipynb index 881a65b..1d324ee 100644 --- a/examples/plot-interface.ipynb +++ b/examples/plot-interface.ipynb @@ -41,7 +41,8 @@ "source": [ "from firefly_client import FireflyClient\n", "import firefly_client.plot as ffplt\n", - "fc = FireflyClient.make_client('https://irsa.ipac.caltech.edu/irsaviewer')\n", + "\n", + "fc = FireflyClient.make_client(\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", "ffplt.use_client(fc)" ] }, @@ -112,9 +113,9 @@ "metadata": {}, "outputs": [], "source": [ - "#import os\n", - "#my_channel = os.environ['USER'] + '-plot-module'\n", - "#ffplt.reset_server(url='https://lsst-demo.ncsa.illinois.edu/firefly', channel=my_channel)" + "# import os\n", + "# my_channel = os.environ['USER'] + '-plot-module'\n", + "# ffplt.reset_server(url='https://lsst-demo.ncsa.illinois.edu/firefly', channel=my_channel)" ] }, { @@ -179,8 +180,10 @@ "outputs": [], "source": [ "m31_table_fname = astropy.utils.data.download_file(\n", - " 'http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl',\n", - " timeout=120, cache=True)" + " \"http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")" ] }, { @@ -196,7 +199,7 @@ "metadata": {}, "outputs": [], "source": [ - "tbl_id = ffplt.upload_table(m31_table_fname, title='M31 2MASS', view_coverage=True)" + "tbl_id = ffplt.upload_table(m31_table_fname, title=\"M31 2MASS\", view_coverage=True)" ] }, { @@ -212,7 +215,7 @@ "metadata": {}, "outputs": [], "source": [ - "ffplt.hist('j_m')" + "ffplt.hist(\"j_m\")" ] }, { @@ -228,7 +231,7 @@ "metadata": {}, "outputs": [], "source": [ - "ffplt.scatter('h_m - k_m', 'j_m')" + "ffplt.scatter(\"h_m - k_m\", \"j_m\")" ] }, { @@ -263,9 +266,12 @@ ], "source": [ "import astropy.io.fits as fits\n", + "\n", "m31_image_fname = astropy.utils.data.download_file(\n", - " 'http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits',\n", - " timeout=120, cache=True)\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits\",\n", + " timeout=120,\n", + " cache=True,\n", + ")\n", "hdu = fits.open(m31_image_fname)\n", "type(hdu)" ] @@ -379,8 +385,11 @@ "metadata": {}, "outputs": [], "source": [ - "wise_tbl_name = astropy.utils.data.download_file('http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl',\n", - " timeout=120, cache=True)" + "wise_tbl_name = astropy.utils.data.download_file(\n", + " \"http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl\",\n", + " timeout=120,\n", + " cache=True,\n", + ")" ] }, { @@ -397,7 +406,8 @@ "outputs": [], "source": [ "from astropy.table import Table\n", - "wise_table = Table.read(wise_tbl_name, format='ipac')" + "\n", + "wise_table = Table.read(wise_tbl_name, format=\"ipac\")" ] }, { @@ -424,7 +434,7 @@ } ], "source": [ - "ffplt.upload_table(wise_table, title='WISE Demo Table')" + "ffplt.upload_table(wise_table, title=\"WISE Demo Table\")" ] }, { diff --git a/firefly_client/env.py b/firefly_client/env.py index 0f552d0..8debba3 100644 --- a/firefly_client/env.py +++ b/firefly_client/env.py @@ -1,7 +1,7 @@ import base64 +import datetime import os import uuid -import datetime try: from .fc_utils import str_2_bool @@ -9,55 +9,80 @@ from fc_utils import str_2_bool # environment variable list -ENV_FF_LAB_EXT = 'fireflyLabExtension' -ENV_FF_CHANNEL_LAB = 'fireflyChannelLab' -ENV_FF_URL_LAB = 'fireflyURLLab' -ENV_FF_URL = 'FIREFLY_URL' -ENV_FF_CHANNEL = 'FIREFLY_CHANNEL' -ENV_FF_HTML = 'FIREFLY_HTML' -ENV_USER = 'USER' +ENV_FF_LAB_EXT = "fireflyLabExtension" +ENV_FF_CHANNEL_LAB = "fireflyChannelLab" +ENV_FF_URL_LAB = "fireflyURLLab" +ENV_FF_URL = "FIREFLY_URL" +ENV_FF_CHANNEL = "FIREFLY_CHANNEL" +ENV_FF_HTML = "FIREFLY_HTML" +ENV_USER = "USER" -EXT_INCORRECT = 'jupyter_firefly_extensions appears to be installed incorrectly.' -SUGGESTION = 'fix jupyter_firefly_extensions in Jupyter Lab or use FireflyClient.make_client()' -COULD_NO_FIND_ENV = 'Could not find environment variable ' +EXT_INCORRECT = "jupyter_firefly_extensions appears to be installed incorrectly." +SUGGESTION = ( + "fix jupyter_firefly_extensions in Jupyter Lab or use FireflyClient.make_client()" +) +COULD_NO_FIND_ENV = "Could not find environment variable " class Env: # all os environment access here - firefly_lab_extension = str_2_bool(os.environ.get(ENV_FF_LAB_EXT, '')) + firefly_lab_extension = str_2_bool(os.environ.get(ENV_FF_LAB_EXT, "")) firefly_channel_lab = os.environ.get(ENV_FF_CHANNEL_LAB) firefly_url_lab = os.environ.get(ENV_FF_URL_LAB) firefly_url = os.environ.get(ENV_FF_URL) firefly_channel_from_env = os.environ.get(ENV_FF_CHANNEL) - firefly_html = os.environ.get(ENV_FF_HTML, '') - user = os.environ.get(ENV_USER, '') + firefly_html = os.environ.get(ENV_FF_HTML, "") + user = os.environ.get(ENV_USER, "") @classmethod def validate_lab_client(cls, generate_lab_ext_channel): - """ return the url and channel or raise an Error """ + """return the url and channel or raise an Error""" not cls.lab_ext_valid() and cls.raise_invalid_lab_error() return cls.firefly_url_lab, cls.resolve_lab_channel(generate_lab_ext_channel) @classmethod def raise_invalid_lab_error(cls): if not cls.firefly_lab_extension: - raise RuntimeError('FireflyClient.makeLabClient can only be used in the Jupyterlab environment. ' + - SUGGESTION) + raise RuntimeError( + "FireflyClient.makeLabClient can only be used in the Jupyterlab environment. " + + SUGGESTION + ) if not cls.firefly_channel_lab: - raise RuntimeError(COULD_NO_FIND_ENV + ENV_FF_CHANNEL_LAB + '. ' + EXT_INCORRECT + ' ' + SUGGESTION) + raise RuntimeError( + COULD_NO_FIND_ENV + + ENV_FF_CHANNEL_LAB + + ". " + + EXT_INCORRECT + + " " + + SUGGESTION + ) if not cls.firefly_url_lab: - raise RuntimeError(COULD_NO_FIND_ENV + ENV_FF_URL_LAB + '. ' + EXT_INCORRECT + ' ' + SUGGESTION) + raise RuntimeError( + COULD_NO_FIND_ENV + + ENV_FF_URL_LAB + + ". " + + EXT_INCORRECT + + " " + + SUGGESTION + ) @classmethod def show_start_browser_tab_msg(cls, url): - print('Firefly URL is {}'.format(url)) - print('To start a new tab you you will have to disable popup blocking for this site.') - print(' Chrome: look at the right side of the address bar') - print(' Firefox: a preference bar appears at the top') - print(' Safari: shows an animation to follow on left side bar') + print("Firefly URL is {}".format(url)) + print( + "To start a new tab you you will have to disable popup blocking for this site." + ) + print(" Chrome: look at the right side of the address bar") + print(" Firefox: a preference bar appears at the top") + print(" Safari: shows an animation to follow on left side bar") @classmethod - def lab_ext_valid(cls): return bool(cls.firefly_lab_extension and cls.firefly_channel_lab and cls.firefly_url_lab) + def lab_ext_valid(cls): + return bool( + cls.firefly_lab_extension + and cls.firefly_channel_lab + and cls.firefly_url_lab + ) @classmethod def resolve_client_channel(cls, in_channel): @@ -66,14 +91,16 @@ def resolve_client_channel(cls, in_channel): elif cls.firefly_channel_from_env: return cls.firefly_channel_from_env else: - start_str = cls.user + datetime.datetime.today().strftime('%Y-%m-%d') - return base64.urlsafe_b64encode(start_str.encode()).decode().replace('=', '') + start_str = cls.user + datetime.datetime.today().strftime("%Y-%m-%d") + return ( + base64.urlsafe_b64encode(start_str.encode()).decode().replace("=", "") + ) @classmethod def resolve_lab_channel(cls, generate_lab_ext_channel): if cls.lab_ext_valid(): if generate_lab_ext_channel: - return cls.firefly_channel_lab + '__lab-external__viewer' + return cls.firefly_channel_lab + "__lab-external__viewer" else: return cls.firefly_channel_lab else: @@ -86,19 +113,25 @@ def find_default_firefly_url(cls): elif cls.firefly_url: return cls.firefly_url else: - return 'http://localhost:8080/firefly' + return "http://localhost:8080/firefly" @classmethod - def find_default_firefly_html(cls): return cls.firefly_html + def find_default_firefly_html(cls): + return cls.firefly_html @classmethod def failed_net_message(cls, location, status_code=-1): - s_str = 'with status: %s' % status_code if (status_code > -1) else '' - check = 'You may want to check the URL with your web browser.\n' - err_message = 'Connection fail to URL %s %s\n%s' % (location, s_str, check) + s_str = "with status: %s" % status_code if (status_code > -1) else "" + check = "You may want to check the URL with your web browser.\n" + err_message = "Connection fail to URL %s %s\n%s" % (location, s_str, check) if cls.firefly_lab_extension and cls.firefly_url_lab: - err_message += ('\nCheck the Firefly URL in ~/.jupyter/jupyter_notebook_config.py' + - ' or ~/.jupyter/jupyter_notebook_config.json') + err_message += ( + "\nCheck the Firefly URL in ~/.jupyter/jupyter_notebook_config.py" + + " or ~/.jupyter/jupyter_notebook_config.json" + ) elif cls.firefly_url: - err_message += 'Check setting of FIREFLY_URL environment variable: %s' % cls.firefly_url + err_message += ( + "Check setting of FIREFLY_URL environment variable: %s" + % cls.firefly_url + ) return err_message diff --git a/firefly_client/fc_utils.py b/firefly_client/fc_utils.py index 11336a2..554567c 100644 --- a/firefly_client/fc_utils.py +++ b/firefly_client/fc_utils.py @@ -1,25 +1,48 @@ +import base64 import json -import urllib.parse import mimetypes -import base64 +import urllib.parse class DebugMarker: firefly_client_debug = False -def str_2_bool(v): return v.lower() in ("yes", "true", "t", "1") -def _make_key(channel, location): return channel+'---'+location -def debug(s): DebugMarker.firefly_client_debug and print('DEBUG: %s' % s) -def warn(s): print('WARNING: %s' % s) -def dict_to_str(in_dict): return json.dumps(in_dict, indent=2, default=str) +def str_2_bool(v): + return v.lower() in ("yes", "true", "t", "1") + + +def _make_key(channel, location): + return channel + "---" + location + + +def debug(s): + DebugMarker.firefly_client_debug and print("DEBUG: %s" % s) + + +def warn(s): + print("WARNING: %s" % s) + + +def dict_to_str(in_dict): + return json.dumps(in_dict, indent=2, default=str) # id for table, region layer, extension -_item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0, 'MaskLayer': 0, 'XYPlot': 0, - 'Cell': 0, 'Histogram': 0, 'Plotly': 0, 'Image': 0, 'FootprintLayer': 0} +_item_id = { + "Table": 0, + "RegionLayer": 0, + "Extension": 0, + "MaskLayer": 0, + "XYPlot": 0, + "Cell": 0, + "Histogram": 0, + "Plotly": 0, + "Image": 0, + "FootprintLayer": 0, +} -ALL = 'ALL_EVENTS_ENABLED' +ALL = "ALL_EVENTS_ENABLED" def gen_item_id(item): @@ -39,75 +62,84 @@ def gen_item_id(item): if item in _item_id: _item_id[item] += 1 - return item + '-' + str(_item_id[item]) + return item + "-" + str(_item_id[item]) else: return None def create_image_url(image_source): - def is_url(url): return urllib.parse.urlparse(url).scheme != '' + def is_url(url): + return urllib.parse.urlparse(url).scheme != "" - if not image_source.startswith('data:image') and not is_url(image_source): + if not image_source.startswith("data:image") and not is_url(image_source): mime, _ = mimetypes.guess_type(image_source) - with open(image_source, 'rb') as fp: + with open(image_source, "rb") as fp: data = fp.read() - data_uri = b''.join(base64.encodestring(data).splitlines()) - return 'data:%s;base64,%s' % (mime, data_uri) + data_uri = b"".join(base64.urlsafe_b64encode(data).splitlines()) + return "data:%s;base64,%s" % (mime, data_uri) return image_source def ensure3(val, name): - """ Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError """ - ret = val if type(val) == list else [val, val, val] + """Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError""" + ret = val if isinstance(val, list) else [val, val, val] if not len(ret) == 3: - raise ValueError('%s list should have 3 items' % name) + raise ValueError("%s list should have 3 items" % name) return ret # actions from Firefly ACTION_DICT = { - 'ShowFits': 'ImagePlotCntlr.PlotImage', - 'AddExtension': 'ExternalAccessCntlr/extensionAdd', - 'FetchTable': 'table.fetch', - 'ShowTable': 'table.search', - 'TableFilter': 'table.filter', - 'TableSort': 'table.sort', - 'ShowXYPlot': 'charts.data/chartAdd', - 'ShowPlot': 'charts.data/chartAdd', - 'ZoomImage': 'ImagePlotCntlr.ZoomImage', - 'PanImage': 'ImagePlotCntlr.recenter', - 'AlignImages': 'ImagePlotCntlr.wcsMatch', - 'StretchImage': 'ImagePlotCntlr.StretchChange', - 'ColorImage': 'ImagePlotCntlr.ColorChange', - 'CreateRegionLayer': 'DrawLayerCntlr.RegionPlot.createLayer', - 'DeleteRegionLayer': 'DrawLayerCntlr.RegionPlot.deleteLayer', - 'AddRegionData': 'DrawLayerCntlr.RegionPlot.addRegion', - 'RemoveRegionData': 'DrawLayerCntlr.RegionPlot.removeRegion', - 'PlotMask': 'ImagePlotCntlr.plotMask', - 'DeleteOverlayMask': 'ImagePlotCntlr.deleteOverlayPlot', - 'AddCell': 'layout.addCell', - 'SetLayoutMode': 'layout.setLayoutMode', - 'UpdateLayout': 'layout.updateLayout', - 'TriviewLayout': 'layout.triviewLayout', - 'ShowCoverage': 'layout.enableSpecialViewer', - 'ShowImageMetaData': 'layout.enableSpecialViewer', - 'ReinitViewer': 'app_data.reinitApp', - 'ShowHiPS': 'ImagePlotCntlr.PlotHiPS', - 'ShowImageOrHiPS': 'ImagePlotCntlr.plotHiPSOrImage', - 'ImagelineBasedFootprint': 'DrawLayerCntlr.ImageLineBasedFP.imagelineBasedFPCreate', - 'StartLabWindow': 'StartLabWindow', - 'StartBrowserTab': 'StartBrowserTab' + "ShowFits": "ImagePlotCntlr.PlotImage", + "AddExtension": "ExternalAccessCntlr/extensionAdd", + "FetchTable": "table.fetch", + "ShowTable": "table.search", + "TableFilter": "table.filter", + "TableSort": "table.sort", + "ShowXYPlot": "charts.data/chartAdd", + "ShowPlot": "charts.data/chartAdd", + "ZoomImage": "ImagePlotCntlr.ZoomImage", + "PanImage": "ImagePlotCntlr.recenter", + "AlignImages": "ImagePlotCntlr.wcsMatch", + "StretchImage": "ImagePlotCntlr.StretchChange", + "ColorImage": "ImagePlotCntlr.ColorChange", + "CreateRegionLayer": "DrawLayerCntlr.RegionPlot.createLayer", + "DeleteRegionLayer": "DrawLayerCntlr.RegionPlot.deleteLayer", + "AddRegionData": "DrawLayerCntlr.RegionPlot.addRegion", + "RemoveRegionData": "DrawLayerCntlr.RegionPlot.removeRegion", + "PlotMask": "ImagePlotCntlr.plotMask", + "DeleteOverlayMask": "ImagePlotCntlr.deleteOverlayPlot", + "AddCell": "layout.addCell", + "SetLayoutMode": "layout.setLayoutMode", + "UpdateLayout": "layout.updateLayout", + "TriviewLayout": "layout.triviewLayout", + "ShowCoverage": "layout.enableSpecialViewer", + "ShowImageMetaData": "layout.enableSpecialViewer", + "ReinitViewer": "app_data.reinitApp", + "ShowHiPS": "ImagePlotCntlr.PlotHiPS", + "ShowImageOrHiPS": "ImagePlotCntlr.plotHiPSOrImage", + "ImagelineBasedFootprint": "DrawLayerCntlr.ImageLineBasedFP.imagelineBasedFPCreate", + "StartLabWindow": "StartLabWindow", + "StartBrowserTab": "StartBrowserTab", } """Definition of Firefly action (`dict`).""" # layout view type -LO_VIEW_DICT = {'table': 'tables', - 'image': 'images', - 'xyPlot': 'xyPlots', - 'imageMeta': 'tableImageMeta', - 'coverImage': 'coverageImage'} +LO_VIEW_DICT = { + "table": "tables", + "image": "images", + "xyPlot": "xyPlots", + "imageMeta": "tableImageMeta", + "coverImage": "coverageImage", +} """Definition of layout viewer (`dict`).""" # extension type -EXTENSION_TYPE = ['AREA_SELECT', 'LINE_SELECT', 'POINT', 'table.highlight', 'table.select'] +EXTENSION_TYPE = [ + "AREA_SELECT", + "LINE_SELECT", + "POINT", + "table.highlight", + "table.select", +] """Type of plot where the extension is added to (`list` of `str`).""" diff --git a/firefly_client/ffws.py b/firefly_client/ffws.py index 0246792..8eeeaf1 100644 --- a/firefly_client/ffws.py +++ b/firefly_client/ffws.py @@ -1,25 +1,26 @@ -import os +import _thread import json import time -from urllib.parse import urljoin -import math -import base64 import traceback -import _thread from copy import deepcopy +from urllib.parse import urljoin + try: from .env import Env except ImportError: from env import Env try: - from .fc_utils import ALL, debug, warn, dict_to_str, DebugMarker + from .fc_utils import ALL, debug, DebugMarker, dict_to_str, warn except ImportError: - from fc_utils import ALL, debug, warn, dict_to_str, DebugMarker + from fc_utils import ALL, debug, DebugMarker, dict_to_str, warn MAX_CHANNELS = 3 -def _make_key(channel, location): return channel+'---'+location + + +def _make_key(channel, location): + return channel + "---" + location class FFWs: @@ -31,20 +32,26 @@ class FFWs: connections = {} @classmethod - def has(cls, channel, location): return _make_key(channel, location) in cls.connections + def has(cls, channel, location): + return _make_key(channel, location) in cls.connections @classmethod - def get(cls, channel, location): return cls.connections.get(_make_key(channel, location)) + def get(cls, channel, location): + return cls.connections.get(_make_key(channel, location)) @classmethod def _open_ws_connection(cls, channel, wsproto, location, auth_headers, header_cb): key = _make_key(channel, location) if key not in cls.connections: if len(cls.connections) > MAX_CHANNELS: - err_msg = 'You may only use %s channels for a python session' % MAX_CHANNELS + err_msg = ( + "You may only use %s channels for a python session" % MAX_CHANNELS + ) raise ConnectionRefusedError(err_msg) - cls.connections[key] = cls(channel, wsproto, location, auth_headers, header_cb) - debug('starting chan: %s %s url:%s' % (channel, wsproto, location)) + cls.connections[key] = cls( + channel, wsproto, location, auth_headers, header_cb + ) + debug("starting chan: %s %s url:%s" % (channel, wsproto, location)) return cls.connections[key] @classmethod @@ -54,9 +61,20 @@ def close_ws_connection(cls, channel, location): cls.connections.pop(_make_key(channel, location), None) @classmethod - def add_listener(cls, wsproto, auth_headers, channel, location, callback, name=ALL, header_cb=None): + def add_listener( + cls, + wsproto, + auth_headers, + channel, + location, + callback, + name=ALL, + header_cb=None, + ): cls._open_ws_connection(channel, wsproto, location, auth_headers, header_cb) - cls.has(channel, location) and cls.get(channel, location).do_add_listener(callback, name) + cls.has(channel, location) and cls.get(channel, location).do_add_listener( + callback, name + ) @classmethod def remove_listener(cls, channel, location, callback, name=ALL): @@ -72,11 +90,13 @@ def wait_for_events(cls, channel, location): cls.has(channel, location) and cls.get(channel, location).do_run_forever() def __init__(self, channel, wsproto, location, auth_headers, header_cb): - - self.ws_url = urljoin('{}://{}/'.format(wsproto, location), 'sticky/firefly/events?channelID=%s' % channel) + self.ws_url = urljoin( + "{}://{}/".format(wsproto, location), + "sticky/firefly/events?channelID=%s" % channel, + ) self.channel = channel self.location = location - self.channel_headers = {'FF-channel': channel} + self.channel_headers = {"FF-channel": channel} self.listeners = {} self.forever_loop = True @@ -91,33 +111,45 @@ def on_open(wsapp): if not DebugMarker.firefly_client_debug: return try: - debug('on open: Status: %d' % wsapp.sock.handshake_response.status) - debug('response headers: \n%s' % dict_to_str(wsapp.sock.handshake_response.headers)) + debug("on open: Status: %d" % wsapp.sock.handshake_response.status) + debug( + "response headers: \n%s" + % dict_to_str(wsapp.sock.handshake_response.headers) + ) except Exception as open_ex: print(traceback.format_exc()) raise open_ex def on_error(wsapp, exception_from_socket): - warn('Error: Websocket connection failed') + warn("Error: Websocket connection failed") print(exception_from_socket) - warn('Websocket Status: %d' % wsapp.sock.handshake_response.status) - warn('Websocket response headers: \n%s' % dict_to_str(wsapp.sock.handshake_response.headers)) + warn("Websocket Status: %d" % wsapp.sock.handshake_response.status) + warn( + "Websocket response headers: \n%s" + % dict_to_str(wsapp.sock.handshake_response.headers) + ) raise exception_from_socket def threaded_connect(): try: import websocket + socket_headers = self.channel_headers.copy() if auth_headers is not None: socket_headers.update(auth_headers) - self.websocket = websocket.WebSocketApp(url=self.ws_url, header=socket_headers, on_message=on_message, - on_open=on_open, on_error=on_error) + self.websocket = websocket.WebSocketApp( + url=self.ws_url, + header=socket_headers, + on_message=on_message, + on_open=on_open, + on_error=on_error, + ) self.debug_show_env(socket_headers) self.websocket.run_forever(ping_interval=10) self.forever_loop = False - debug('websocket thread ended') + debug("websocket thread ended") except Exception: - debug('websocket thread ended with exception') + debug("websocket thread ended with exception") print(traceback.format_exc()) try: @@ -128,46 +160,55 @@ def threaded_connect(): def debug_show_env(self, socket_headers): if not DebugMarker.firefly_client_debug: return - debug('Attempting to connect\n %s\n %s\n channel: %s' % (self.location, self.ws_url, self.channel)) - debug('Header sent to websocket connections: %s' % dict_to_str(socket_headers)) + debug( + "Attempting to connect\n %s\n %s\n channel: %s" + % (self.location, self.ws_url, self.channel) + ) + debug("Header sent to websocket connections: %s" % dict_to_str(socket_headers)) def debug_header_event_message(self, ev): if not DebugMarker.firefly_client_debug: return - debug('Event: %s' % ev['name']) + debug("Event: %s" % ev["name"]) log_ev = deepcopy(ev) try: - log_ev['data']['plotState']['bandStateAry'] = '<<<<>>>>' + log_ev["data"]["plotState"]["bandStateAry"] = "<<<<>>>>" except KeyError: pass - debug('JSON Data:\n%s' % json.dumps(log_ev, indent=2, default=str)) - debug('All Listeners for channel: %s, location: %s' % (self.channel, self.location)) + debug("JSON Data:\n%s" % json.dumps(log_ev, indent=2, default=str)) + debug( + "All Listeners for channel: %s, location: %s" + % (self.channel, self.location) + ) for callback, eventIDList in self.listeners.items(): debug(" %s" % eventIDList) self.execute_callbacks(ev, do_callback=False) def execute_callbacks(self, ev, do_callback=True): - name = ev['name'] + name = ev["name"] for callback, eventIDList in self.listeners.items(): if name in eventIDList or ALL in eventIDList: - callback(ev) if do_callback else debug('callback: %s' % name) + callback(ev) if do_callback else debug("callback: %s" % name) def received_message(self, message, header_cb): try: ev = json.loads(message) except JSONDecodeError as err: - warn('Error with JSON input - event string could not be parsed') + warn("Error with JSON input - event string could not be parsed") warn(message) warn(err) return - if ev['name'] == 'EVT_CONN_EST': + if ev["name"] == "EVT_CONN_EST": try: - conn_info = ev['data'] - debug('Connection established:\n %s' % message) + conn_info = ev["data"] + debug("Connection established:\n %s" % message) if self.channel is None: - self.channel = conn_info['channel'] - self.channel_headers = {'FF-channel': self.channel, 'FF-connID': conn_info.get('connID')} + self.channel = conn_info["channel"] + self.channel_headers = { + "FF-channel": self.channel, + "FF-connID": conn_info.get("connID"), + } header_cb(self.channel_headers) except Exception as err: print(message) @@ -177,19 +218,18 @@ def received_message(self, message, header_cb): self.execute_callbacks(ev) def disconnect(self): - """Disconnect the WebSocket. - """ + """Disconnect the WebSocket.""" self.websocket.close() def do_add_listener(self, callback, name=ALL): - debug('adding listener to %s, %s' % (self.channel, self.ws_url)) + debug("adding listener to %s, %s" % (self.channel, self.ws_url)) if callback not in self.listeners.keys(): self.listeners[callback] = [] if name not in self.listeners[callback]: self.listeners[callback].append(name) def do_remove_listener(self, callback, name=ALL): - debug('removing listener to %s, %s' % (self.channel, self.ws_url)) + debug("removing listener to %s, %s" % (self.channel, self.ws_url)) if callback in self.listeners.keys(): if name in self.listeners[callback]: self.listeners[callback].remove(name) diff --git a/firefly_client/firefly_client.py b/firefly_client/firefly_client.py index 3658723..ec891fe 100644 --- a/firefly_client/firefly_client.py +++ b/firefly_client/firefly_client.py @@ -4,15 +4,17 @@ This module defines class 'FireflyClient' and methods to remotely communicate to Firefly viewer by dispatching remote actions. """ -import requests -import webbrowser + import json -import time -import socket -from urllib.parse import urljoin import math +import socket +import time import weakref +import webbrowser from copy import copy +from urllib.parse import urljoin + +import requests try: @@ -28,19 +30,39 @@ except ImportError: from range_values import RangeValues try: - from .fc_utils import debug, warn, dict_to_str, create_image_url, ensure3, gen_item_id,\ - DebugMarker, ALL, ACTION_DICT, LO_VIEW_DICT + from .fc_utils import ( + ACTION_DICT, + ALL, + create_image_url, + debug, + DebugMarker, + dict_to_str, + ensure3, + gen_item_id, + LO_VIEW_DICT, + warn, + ) except ImportError: - from fc_utils import debug, warn, dict_to_str, create_image_url, ensure3, gen_item_id,\ - DebugMarker, ALL, ACTION_DICT, LO_VIEW_DICT - -__docformat__ = 'restructuredtext' + from fc_utils import ( + ACTION_DICT, + ALL, + create_image_url, + debug, + DebugMarker, + dict_to_str, + ensure3, + gen_item_id, + LO_VIEW_DICT, + warn, + ) + +__docformat__ = "restructuredtext" _def_html_file = Env.find_default_firefly_html() _default_url = Env.find_default_firefly_url() -BROWSER = 'browser' -LAB = 'lab' -UNKNOWN = 'UNKNOWN' +BROWSER = "browser" +LAB = "lab" +UNKNOWN = "UNKNOWN" class FireflyClient: @@ -74,40 +96,40 @@ class FireflyClient: in the sessions attribute. """ - TAB_ID = 'firefly-viewer-tab-id' - TRIVIEW_ICov_Ch_T = 'TRIVIEW_ICov_Ch_T' - TRIVIEW_I_ChCov_T = 'TRIVIEW_I_ChCov_T' - BIVIEW_ICov_Ch = 'BIVIEW_ICov_Ch' - BIVIEW_I_ChCov = 'BIVIEW_I_ChCov' - BIVIEW_T_IChCov = 'BIVIEW_T_IChCov' - BIVIEW_IChCov_T = 'BIVIEW_IChCov_T' + TAB_ID = "firefly-viewer-tab-id" + TRIVIEW_ICov_Ch_T = "TRIVIEW_ICov_Ch_T" + TRIVIEW_I_ChCov_T = "TRIVIEW_I_ChCov_T" + BIVIEW_ICov_Ch = "BIVIEW_ICov_Ch" + BIVIEW_I_ChCov = "BIVIEW_I_ChCov" + BIVIEW_T_IChCov = "BIVIEW_T_IChCov" + BIVIEW_IChCov_T = "BIVIEW_IChCov_T" tri_view_types_list = [ TRIVIEW_ICov_Ch_T, TRIVIEW_I_ChCov_T, BIVIEW_ICov_Ch, BIVIEW_I_ChCov, BIVIEW_T_IChCov, - BIVIEW_IChCov_T + BIVIEW_IChCov_T, ] tri_view_layout_desc = { - TRIVIEW_ICov_Ch_T: 'top left: image/cov, top right: charts, bottom: tables', - TRIVIEW_I_ChCov_T: 'top left: image, top right: charts/cov, bottom: tables', - BIVIEW_ICov_Ch: 'left: image/cov, right: charts', - BIVIEW_I_ChCov: 'left: image, right: charts/cov', - BIVIEW_T_IChCov: 'left: tables, right: image/charts/cov', - BIVIEW_IChCov_T: 'left: image/charts/cov, right: tables', + TRIVIEW_ICov_Ch_T: "top left: image/cov, top right: charts, bottom: tables", + TRIVIEW_I_ChCov_T: "top left: image, top right: charts/cov, bottom: tables", + BIVIEW_ICov_Ch: "left: image/cov, right: charts", + BIVIEW_I_ChCov: "left: image, right: charts/cov", + BIVIEW_T_IChCov: "left: tables, right: image/charts/cov", + BIVIEW_IChCov_T: "left: image/charts/cov, right: tables", } # viewer modes - TRIVIEW_VIEWER = 'FireflyViewer' - SLATE_VIEWER = 'FireflySlate' - NO_VIEWER = 'NO_VIEWER' - _viewer_modes = [TRIVIEW_VIEWER,SLATE_VIEWER,NO_VIEWER] + TRIVIEW_VIEWER = "FireflyViewer" + SLATE_VIEWER = "FireflySlate" + NO_VIEWER = "NO_VIEWER" + _viewer_modes = [TRIVIEW_VIEWER, SLATE_VIEWER, NO_VIEWER] # viewer ids - PINNED_CHART_VIEWER_ID = 'PINNED_CHART_VIEWER_ID' - PINNED_IMAGE_VIEWER_ID = 'DEFAULT_FITS_VIEWER_ID' + PINNED_CHART_VIEWER_ID = "PINNED_CHART_VIEWER_ID" + PINNED_IMAGE_VIEWER_ID = "DEFAULT_FITS_VIEWER_ID" _debug = False # Keep track of instances. @@ -116,12 +138,28 @@ class FireflyClient: """All events are enabled for the listener (`str`).""" # id for table, region layer, extension - _item_id = {'Table': 0, 'RegionLayer': 0, 'Extension': 0, 'MaskLayer': 0, 'XYPlot': 0, - 'Cell': 0, 'Histogram': 0, 'Plotly': 0, 'Image': 0, 'FootprintLayer': 0} + _item_id = { + "Table": 0, + "RegionLayer": 0, + "Extension": 0, + "MaskLayer": 0, + "XYPlot": 0, + "Cell": 0, + "Histogram": 0, + "Plotly": 0, + "Image": 0, + "FootprintLayer": 0, + } @classmethod - def make_lab_client(cls, start_browser_tab=False, html_file=_def_html_file, start_tab=True, - verbose=False, token=None): + def make_lab_client( + cls, + start_browser_tab=False, + html_file=_def_html_file, + start_tab=True, + verbose=False, + token=None, + ): """ Factory method to create a Firefly client in the Jupyterlab environment. If you are using Jupyterlab with the jupyter_firefly_extension installed, @@ -157,17 +195,31 @@ def make_lab_client(cls, start_browser_tab=False, html_file=_def_html_file, star out : `FireflyClient` A FireflyClient that works in the lab environment """ - tab_type = BROWSER if (start_browser_tab and start_tab) else (LAB if start_tab else None) + tab_type = ( + BROWSER + if (start_browser_tab and start_tab) + else (LAB if start_tab else None) + ) url, channel = Env.validate_lab_client(tab_type == BROWSER) fc = cls(url, channel, html_file, token) if tab_type: - verbose and tab_type == BROWSER and Env.show_start_browser_tab_msg(fc.get_firefly_url()) + verbose and tab_type == BROWSER and Env.show_start_browser_tab_msg( + fc.get_firefly_url() + ) fc._lab_env_tab_start(tab_type, html_file) return fc @classmethod - def make_client(cls, url=_default_url, html_file=_def_html_file, launch_browser=True, - channel_override=None, verbose=False, token=None, viewer_override=None): + def make_client( + cls, + url=_default_url, + html_file=_def_html_file, + launch_browser=True, + channel_override=None, + verbose=False, + token=None, + viewer_override=None, + ): """ Factory method to create a Firefly client in a plain Python, IPython, or notebook session, and attempt to open a display. If a display cannot be @@ -213,55 +265,80 @@ def make_client(cls, url=_default_url, html_file=_def_html_file, launch_browser= fc : `FireflyClient` A FireflyClient that works in the lab environment """ - fc = cls(url, Env.resolve_client_channel(channel_override), html_file, token, viewer_override) + fc = cls( + url, + Env.resolve_client_channel(channel_override), + html_file, + token, + viewer_override, + ) verbose and Env.show_start_browser_tab_msg(fc.get_firefly_url()) launch_browser and fc.launch_browser() return fc - def __init__(self, url, channel, html_file=_def_html_file, token=None, viewer_override=None): + def __init__( + self, url, channel, html_file=_def_html_file, token=None, viewer_override=None + ): DebugMarker.firefly_client_debug = FireflyClient._debug FireflyClient.instances.append(weakref.ref(self)) - ssl = url.startswith('https://') - self.wsproto = 'wss' if ssl else 'ws' + ssl = url.startswith("https://") + self.wsproto = "wss" if ssl else "ws" self.location = url[8:] if ssl else url[7:] - self.location = self.location[:-1] if self.location.endswith('/') else self.location + self.location = ( + self.location[:-1] if self.location.endswith("/") else self.location + ) self.url = url self.channel = channel self.render_tree_id = None - self.auth_headers = {'Authorization': 'Bearer {}'.format(token)} if token and ssl else None - self.header_from_ws = {'FF-channel': channel} + self.auth_headers = [] + if token and ssl: + self.auth_headers.append(("Authorization", f"Bearer {token}")) + self.header_from_ws = {"FF-channel": channel} self.lab_env_tab_type = UNKNOWN # urls for cmd service and browser - protocol = 'https' if ssl else 'http' - self.url_cmd_service = urljoin('{}://{}/'.format(protocol, self.location), 'sticky/CmdSrv') - self.url_browser = urljoin(urljoin('{}://{}/'.format(protocol, self.location), html_file), '?__wsch=') + protocol = "https" if ssl else "http" + self.url_cmd_service = urljoin( + "{}://{}/".format(protocol, self.location), "sticky/CmdSrv" + ) + self.url_browser = urljoin( + urljoin("{}://{}/".format(protocol, self.location), html_file), "?__wsch=" + ) self.url_bw = self.url_browser # keep around for backward compatibility self.session = requests.Session() token and ssl and self.session.headers.update(self.auth_headers) - not ssl and token and warn('token ignored: should be None when url starts with http://') - self.firefly_viewer = FireflyClient.get_viewer_mode(html_file,viewer_override) - debug('new instance: %s' % url) + not ssl and token and warn( + "token ignored: should be None when url starts with http://" + ) + self.firefly_viewer = FireflyClient.get_viewer_mode(html_file, viewer_override) + debug("new instance: %s" % url) def _lab_env_tab_start(self, tab_type, html_file): - """start a tab in the lab environment, tab_type must be 'lab' or 'browser' """ + """start a tab in the lab environment, tab_type must be 'lab' or 'browser'""" self.lab_env_tab_type = tab_type if tab_type == BROWSER: - idx = self.channel.find('__viewer') - c = self.channel[0:idx] if idx > -1 else self.channel # the ext will add '__viewer' so I have to remove it - self.dispatch(ACTION_DICT['StartBrowserTab'], - {'channel': c, 'fireflyHtmlFile': _def_html_file}, Env.firefly_channel_lab) + idx = self.channel.find("__viewer") + c = ( + self.channel[0:idx] if idx > -1 else self.channel + ) # the ext will add '__viewer' so I have to remove it + self.dispatch( + ACTION_DICT["StartBrowserTab"], + {"channel": c, "fireflyHtmlFile": _def_html_file}, + Env.firefly_channel_lab, + ) elif tab_type == LAB: if not self.render_tree_id: - self.render_tree_id = FireflyClient.TAB_ID # no longer generating redner_tree_id + self.render_tree_id = ( + FireflyClient.TAB_ID + ) # no longer generating redner_tree_id # self.render_tree_id = 'slateClient-%s-%s' % (len(self.instances), round(time.time())) self.show_lab_tab(html_file) def show_lab_tab(self, html_file=_def_html_file): """If using a jupyter lab tab - show it or reopen it. If not using a lab tab then noop""" - self.lab_env_tab_type = (LAB and - self.dispatch(ACTION_DICT['StartLabWindow'], - {'fireflyHtmlFile': html_file})) + self.lab_env_tab_type = LAB and self.dispatch( + ACTION_DICT["StartLabWindow"], {"fireflyHtmlFile": html_file} + ) @staticmethod def get_viewer_mode(html_file, viewer_override): @@ -269,16 +346,28 @@ def get_viewer_mode(html_file, viewer_override): if viewer_override in FireflyClient._viewer_modes: return viewer_override else: - warn('viewer_override mode: {} is not a recognized mode, using {}'.format(viewer_override, UNKNOWN)) + warn( + "viewer_override mode: {} is not a recognized mode, using {}".format( + viewer_override, UNKNOWN + ) + ) return UNKNOWN else: - return FireflyClient.SLATE_VIEWER if html_file == 'slate.html' else FireflyClient.TRIVIEW_VIEWER + return ( + FireflyClient.SLATE_VIEWER + if html_file == "slate.html" + else FireflyClient.TRIVIEW_VIEWER + ) def _send_url_as_get(self, url): return self.call_response(self.session.get(url, headers=self.header_from_ws)) def _send_url_as_post(self, data): - return self.call_response(self.session.post(self.url_cmd_service, data=data, headers=self.header_from_ws)) + return self.call_response( + self.session.post( + self.url_cmd_service, data=data, headers=self.header_from_ws + ) + ) def call_response(self, response): if response.status_code != 200: @@ -287,47 +376,49 @@ def call_response(self, response): status = json.loads(response.text) return status[0] except ValueError as err: - warn('JSON parsing Error:') + warn("JSON parsing Error:") if len(response.text) > 300: - warn('Response string (first 300 characters):\n' + response.text[0:300]) - debug('Full Response:\n' + response.text) + warn("Response string (first 300 characters):\n" + response.text[0:300]) + debug("Full Response:\n" + response.text) else: - warn('Response string:\n' + response.text[0:300]) + warn("Response string:\n" + response.text[0:300]) raise err - + @staticmethod def _get_ip(): """Find local IP address, based on https://stackoverflow.com/q/166506/8252556.""" s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: - s.connect(('8.8.8.8', 1)) # doesn't even have to be reachable + s.connect(("8.8.8.8", 1)) # doesn't even have to be reachable ip = s.getsockname()[0] except Exception: - ip = '127.0.0.1' + ip = "127.0.0.1" finally: s.close() return ip def _is_page_connected(self): """Check if the page is connected.""" - url = f'{self.url_cmd_service}?cmd=pushAliveCheck&ipAddress={self._get_ip()}' + url = f"{self.url_cmd_service}?cmd=pushAliveCheck&ipAddress={self._get_ip()}" retval = self._send_url_as_get(url) - return retval['active'] + return retval["active"] - def is_triview(self): return self.firefly_viewer == FireflyClient.TRIVIEW_VIEWER + def is_triview(self): + return self.firefly_viewer == FireflyClient.TRIVIEW_VIEWER - def is_slate(self): return self.firefly_viewer == FireflyClient.SLATE_VIEWER + def is_slate(self): + return self.firefly_viewer == FireflyClient.SLATE_VIEWER @staticmethod def _make_pid_param(plot_id): - return ','.join(plot_id) if isinstance(plot_id, list) else plot_id + return ",".join(plot_id) if isinstance(plot_id, list) else plot_id -# ----------------------------------------------------------------- -# ----------------------------------------------------------------- -# Public API Begins -# ----------------------------------------------------------------- -# ----------------------------------------------------------------- + # ----------------------------------------------------------------- + # ----------------------------------------------------------------- + # Public API Begins + # ----------------------------------------------------------------- + # ----------------------------------------------------------------- def add_listener(self, callback, name=ALL): """ Add a callback function to listen for events on the Firefly client. @@ -345,8 +436,19 @@ def add_listener(self, callback, name=ALL): """ try: - def header_cb(headers): self.header_from_ws = headers - FFWs.add_listener(self.wsproto, self.auth_headers, self.channel, self.location, callback, name, header_cb) + + def header_cb(headers): + self.header_from_ws = headers + + FFWs.add_listener( + self.wsproto, + self.auth_headers, + self.channel, + self.location, + callback, + name, + header_cb, + ) except ConnectionRefusedError as err: raise ValueError(f"Couldn't add listener: {err}") from err @@ -382,8 +484,7 @@ def wait_for_events(self): FFWs.wait_for_events(self.channel, self.location) def disconnect(self): - """DEPRECATED. Now just remove the listeners. Disconnect the WebSocket. - """ + """DEPRECATED. Now just remove the listeners. Disconnect the WebSocket.""" FFWs.close_ws_connection(self.channel, self.location) def get_firefly_url(self, channel=None): @@ -417,12 +518,19 @@ def display_url(self, url=None): try: ipy_str = str(type(get_ipython())) except NameError: - ipy_str = '' - if 'zmqshell' in ipy_str: + ipy_str = "" + if "zmqshell" in ipy_str: from IPython.display import display, HTML - display(HTML('Open your web browser to this link'.format(url))) + + display( + HTML( + 'Open your web browser to this link'.format( + url + ) + ) + ) else: - print('Open your web browser to {}'.format(url)) + print("Open your web browser to {}".format(url)) def launch_browser(self, channel=None, force=False, verbose=True): """ @@ -493,13 +601,13 @@ def upload_file(self, path): .. note:: 'pre_load' is not implemented in the server (will be removed later). """ - url = self.url_cmd_service + '?cmd=upload' - files = {'file': open(path, 'rb')} + url = self.url_cmd_service + "?cmd=upload" + files = {"file": open(path, "rb")} result = self.session.post(url, files=files, headers=self.header_from_ws) if result.status_code == 200: - index = result.text.find('$') + index = result.text.find("$") return result.text[index:] - raise requests.HTTPError('Upload unsuccessful') + raise requests.HTTPError("Upload unsuccessful") def upload_fits_data(self, stream): """ @@ -517,7 +625,7 @@ def upload_fits_data(self, stream): out : `dict` Status, like {'success': True}. """ - return self.upload_data(stream, 'FITS') + return self.upload_data(stream, "FITS") def upload_text_data(self, stream): """ @@ -535,7 +643,7 @@ def upload_text_data(self, stream): out : `dict` Status, like {'success': True}. """ - return self.upload_data(stream, 'UNKNOWN') + return self.upload_data(stream, "UNKNOWN") def upload_data(self, stream, data_type): """ @@ -555,15 +663,15 @@ def upload_data(self, stream, data_type): Status, like {'success': True}. """ - url = self.url_cmd_service + '?cmd=upload&preload=' - url += 'true&type=FITS' if data_type.upper() == 'FITS' else 'false&type=UNKNOWN' + url = self.url_cmd_service + "?cmd=upload&preload=" + url += "true&type=FITS" if data_type.upper() == "FITS" else "false&type=UNKNOWN" stream.seek(0, 0) - data_pack = {'data': stream} + data_pack = {"data": stream} result = self.session.post(url, files=data_pack, headers=self.header_from_ws) if result.status_code == 200: - index = result.text.find('$') + index = result.text.find("$") return result.text[index:] - raise requests.HTTPError('Upload unsuccessful') + raise requests.HTTPError("Upload unsuccessful") @staticmethod def create_image_url(image_source): @@ -604,11 +712,14 @@ def dispatch(self, action_type, payload, override_channel=None): if payload is None: payload = {} if self.render_tree_id: - payload['renderTreeId'] = self.render_tree_id + payload["renderTreeId"] = self.render_tree_id channel = self.channel if override_channel is None else override_channel - action = {'type': action_type, 'payload': payload} - data = {'channelID': channel, 'cmd': 'pushAction', 'action': json.dumps(action)} - debug('dispatch: type: %s, channel: %s \n%s' % (action_type, channel, dict_to_str(action))) + action = {"type": action_type, "payload": payload} + data = {"channelID": channel, "cmd": "pushAction", "action": json.dumps(action)} + debug( + "dispatch: type: %s, channel: %s \n%s" + % (action_type, channel, dict_to_str(action)) + ) return self._send_url_as_post(data) @@ -638,13 +749,21 @@ def change_triview_layout(self, layout): """ if not self.is_triview(): - return {'success': True, 'warning': 'change_triview_layout ignored when not in triview mode'} + return { + "success": True, + "warning": "change_triview_layout ignored when not in triview mode", + } if layout not in FireflyClient.tri_view_types_list: - warning = '{} is an unknown layout type, valid types: {}'.format(layout, FireflyClient.tri_view_types_list) - return {'success': False, 'warning': warning} + warning = "{} is an unknown layout type, valid types: {}".format( + layout, FireflyClient.tri_view_types_list + ) + return {"success": False, "warning": warning} - self.dispatch(ACTION_DICT['TriviewLayout'], {'triviewLayout': layout}) - return {'success': True, 'description': FireflyClient.tri_view_layout_desc[layout]} + self.dispatch(ACTION_DICT["TriviewLayout"], {"triviewLayout": layout}) + return { + "success": True, + "description": FireflyClient.tri_view_layout_desc[layout], + } def add_cell(self, row, col, width, height, element_type, cell_id=None): """ @@ -671,26 +790,31 @@ def add_cell(self, row, col, width, height, element_type, cell_id=None): Status of the request, like {'success': True, 'cell_id': 'Cell-1'}. """ if not self.is_slate(): - return {'success': True, 'cell_id': cell_id if cell_id else 'noop', - 'warning': 'add_cell ignored when not in slate mode'} + return { + "success": True, + "cell_id": cell_id if cell_id else "noop", + "warning": "add_cell ignored when not in slate mode", + } # force the cell_id to be 'main' for table's case - if element_type == LO_VIEW_DICT['table']: - if not cell_id or cell_id != 'main': - cell_id = 'main' + if element_type == LO_VIEW_DICT["table"]: + if not cell_id or cell_id != "main": + cell_id = "main" else: if not cell_id: - cell_id = gen_item_id('Cell') - - payload = {'row': row, - 'col': col, - 'width': width, - 'height': height, - 'type': element_type, - 'cellId': cell_id} - - r = self.dispatch(ACTION_DICT['AddCell'], payload) - r.update({'cell_id': cell_id}) + cell_id = gen_item_id("Cell") + + payload = { + "row": row, + "col": col, + "width": width, + "height": height, + "type": element_type, + "cellId": cell_id, + } + + r = self.dispatch(ACTION_DICT["AddCell"], payload) + r.update({"cell_id": cell_id}) return r def reinit_viewer(self): @@ -702,9 +826,11 @@ def reinit_viewer(self): out : `dict` Status of the request, like {'success': True}. """ - return self.dispatch(ACTION_DICT['ReinitViewer'], {}) + return self.dispatch(ACTION_DICT["ReinitViewer"], {}) - def show_fits(self, file_on_server=None, plot_id=None, viewer_id=None, **additional_params): + def show_fits( + self, file_on_server=None, plot_id=None, viewer_id=None, **additional_params + ): """ Show a FITS image. @@ -744,23 +870,25 @@ def show_fits(self, file_on_server=None, plot_id=None, viewer_id=None, **additio is used for image search. """ - wp_request = {'plotGroupId': 'groupFromPython', - 'GroupLocked': False} - payload = {'wpRequest': wp_request, - 'useContextModifications': True} + wp_request = {"plotGroupId": "groupFromPython", "GroupLocked": False} + payload = {"wpRequest": wp_request, "useContextModifications": True} warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload.update({'viewerId': viewer_id}) - plot_id and payload['wpRequest'].update({'plotId': plot_id}) - file_on_server and payload['wpRequest'].update({'file': file_on_server}) - additional_params and payload['wpRequest'].update(additional_params) + payload.update({"viewerId": viewer_id}) + plot_id and payload["wpRequest"].update({"plotId": plot_id}) + file_on_server and payload["wpRequest"].update({"file": file_on_server}) + additional_params and payload["wpRequest"].update(additional_params) - r = self.dispatch(ACTION_DICT['ShowFits'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowFits"], payload) + warning and r.update({"warning": warning}) return r def show_fits_3color(self, three_color_params, plot_id=None, viewer_id=None): @@ -786,28 +914,67 @@ def show_fits_3color(self, three_color_params, plot_id=None, viewer_id=None): Status of the request, like {'success': True}. """ - three_color = three_color_params if type(three_color_params).__name__ == 'list' else [three_color_params] + three_color = ( + three_color_params + if type(three_color_params).__name__ == "list" + else [three_color_params] + ) for item in three_color: - item.update({'GroupLocked': item.get('GroupLocked') if 'GroupLocked' in item else False}) - item.update({'plotGroupId': item.get('plotGroupId') if 'plotGroupId' in item else 'groupFromPython'}) - item.update({'Title': item.get('Title') if 'Title' in item else '3 Color'}) - if 'plotId' not in item and plot_id: - item.update({'plotId': plot_id}) - - payload = {'wpRequest': three_color, 'threeColor': True, 'useContextModifications': True} + item.update( + { + "GroupLocked": ( + item.get("GroupLocked") if "GroupLocked" in item else False + ) + } + ) + item.update( + { + "plotGroupId": ( + item.get("plotGroupId") + if "plotGroupId" in item + else "groupFromPython" + ) + } + ) + item.update({"Title": item.get("Title") if "Title" in item else "3 Color"}) + if "plotId" not in item and plot_id: + item.update({"plotId": plot_id}) + + payload = { + "wpRequest": three_color, + "threeColor": True, + "useContextModifications": True, + } warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload.update({'viewerId': viewer_id}) + payload.update({"viewerId": viewer_id}) - r = self.dispatch(ACTION_DICT['ShowFits'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowFits"], payload) + warning and r.update({"warning": warning}) return r - def show_table(self, file_on_server=None, url=None, tbl_id=None, title=None, page_size=100, is_catalog=True, - meta=None, target_search_info=None, options=None, table_index=None, - column_spec=None, filters=None, visible=True): + def show_table( + self, + file_on_server=None, + url=None, + tbl_id=None, + title=None, + page_size=100, + is_catalog=True, + meta=None, + target_search_info=None, + options=None, + table_index=None, + column_spec=None, + filters=None, + visible=True, + ): """ Show a table. @@ -890,38 +1057,63 @@ def show_table(self, file_on_server=None, url=None, tbl_id=None, title=None, pag """ if not tbl_id: - tbl_id = gen_item_id('Table') + tbl_id = gen_item_id("Table") if not title: - title = tbl_id if file_on_server or url else target_search_info.get('catalog', tbl_id) + title = ( + tbl_id + if file_on_server or url + else target_search_info.get("catalog", tbl_id) + ) - meta_info = {'title': title, 'tbl_id': tbl_id} + meta_info = {"title": title, "tbl_id": tbl_id} meta and meta_info.update(meta) - tbl_req = {'startIdx': 0, 'pageSize': page_size, 'tbl_id': tbl_id} + tbl_req = {"startIdx": 0, "pageSize": page_size, "tbl_id": tbl_id} if file_on_server or url: - tbl_type = 'table' if not is_catalog else 'catalog' + tbl_type = "table" if not is_catalog else "catalog" source = url if url else file_on_server - tbl_req.update({'source': source, 'tblType': tbl_type, - 'id': 'IpacTableFromSource'}) - table_index and tbl_req.update({'tbl_index': table_index}) + tbl_req.update( + {"source": source, "tblType": tbl_type, "id": "IpacTableFromSource"} + ) + table_index and tbl_req.update({"tbl_index": table_index}) elif target_search_info: target_search_info.update( - {'use': target_search_info.get('use') if 'use' in target_search_info else 'catalog_overlay'}) - tbl_req.update({'id': 'GatorQuery', 'UserTargetWorldPt': target_search_info.get('position')}) - target_search_info.pop('position', None) + { + "use": ( + target_search_info.get("use") + if "use" in target_search_info + else "catalog_overlay" + ) + } + ) + tbl_req.update( + { + "id": "GatorQuery", + "UserTargetWorldPt": target_search_info.get("position"), + } + ) + target_search_info.pop("position", None) tbl_req.update(target_search_info) - tbl_req.update({'META_INFO': meta_info}) - options and tbl_req.update({'options': options}) - column_spec and tbl_req.update({'inclCols': column_spec}) - filters and tbl_req.update({'filters': filters}) + tbl_req.update({"META_INFO": meta_info}) + options and tbl_req.update({"options": options}) + column_spec and tbl_req.update({"inclCols": column_spec}) + filters and tbl_req.update({"filters": filters}) - payload = {'request': tbl_req} - action_type = ACTION_DICT['ShowTable'] if visible else ACTION_DICT['FetchTable'] + payload = {"request": tbl_req} + action_type = ACTION_DICT["ShowTable"] if visible else ACTION_DICT["FetchTable"] return self.dispatch(action_type, payload) - def fetch_table(self, file_on_server, tbl_id=None, title=None, page_size=1, table_index=None, meta=None): + def fetch_table( + self, + file_on_server, + tbl_id=None, + title=None, + page_size=1, + table_index=None, + meta=None, + ): """ Fetch table data without showing them @@ -950,19 +1142,24 @@ def fetch_table(self, file_on_server, tbl_id=None, title=None, page_size=1, tabl Status of the request, like {'success': True}. """ if not tbl_id: - tbl_id = gen_item_id('Table') + tbl_id = gen_item_id("Table") if not title: title = tbl_id - tbl_req = {'startIdx': 0, 'pageSize': page_size, 'source': file_on_server, - 'id': 'IpacTableFromSource', 'tbl_id': tbl_id} + tbl_req = { + "startIdx": 0, + "pageSize": page_size, + "source": file_on_server, + "id": "IpacTableFromSource", + "tbl_id": tbl_id, + } if table_index: - tbl_req.update({'tbl_index': table_index}) + tbl_req.update({"tbl_index": table_index}) - meta_info = {'title': title, 'tbl_id': tbl_id} + meta_info = {"title": title, "tbl_id": tbl_id} meta and meta_info.update(meta) - tbl_req.update({'META_INFO': meta_info}) - payload = {'request': tbl_req, 'hlRowIdx': 0} - return self.dispatch(ACTION_DICT['FetchTable'], payload) + tbl_req.update({"META_INFO": meta_info}) + payload = {"request": tbl_req, "hlRowIdx": 0} + return self.dispatch(ACTION_DICT["FetchTable"], payload) def show_xyplot(self, tbl_id, standalone=False, group_id=None, **chart_params): """ @@ -1021,24 +1218,32 @@ def show_xyplot(self, tbl_id, standalone=False, group_id=None, **chart_params): parameters are valid. """ - cid = gen_item_id('XYPlot') + cid = gen_item_id("XYPlot") warning = None if self.is_triview(): - warning = 'group_id unnecessary and ignored in triview mode' if group_id else None + warning = ( + "group_id unnecessary and ignored in triview mode" if group_id else None + ) group_id = FireflyClient.PINNED_CHART_VIEWER_ID elif not group_id: - group_id = 'default' if standalone else tbl_id - - payload = {'chartId': cid, 'chartType': 'scatter', - 'groupId': group_id, 'viewerId': group_id, - 'params': {'tbl_id': tbl_id, **chart_params}} - - r = self.dispatch(ACTION_DICT['ShowXYPlot'], payload) - warning and r.update({'warning': warning}) + group_id = "default" if standalone else tbl_id + + payload = { + "chartId": cid, + "chartType": "scatter", + "groupId": group_id, + "viewerId": group_id, + "params": {"tbl_id": tbl_id, **chart_params}, + } + + r = self.dispatch(ACTION_DICT["ShowXYPlot"], payload) + warning and r.update({"warning": warning}) return r - def show_histogram(self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_params): + def show_histogram( + self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_params + ): """ Show a histogram @@ -1076,17 +1281,22 @@ def show_histogram(self, tbl_id, group_id=PINNED_CHART_VIEWER_ID, **histogram_pa warning = None if self.is_triview(): - warning = 'group_id unnecessary and ignored in triview mode' if group_id else None + warning = ( + "group_id unnecessary and ignored in triview mode" if group_id else None + ) group_id = FireflyClient.PINNED_CHART_VIEWER_ID - cid = gen_item_id('Histogram') - payload = {'chartId': cid, 'chartType': 'histogram', - 'groupId': group_id, - 'viewerId': group_id, - 'params': {'tbl_id': tbl_id, **histogram_params}} - - r = self.dispatch(ACTION_DICT['ShowXYPlot'], payload) - warning and r.update({'warning': warning}) + cid = gen_item_id("Histogram") + payload = { + "chartId": cid, + "chartType": "histogram", + "groupId": group_id, + "viewerId": group_id, + "params": {"tbl_id": tbl_id, **histogram_params}, + } + + r = self.dispatch(ACTION_DICT["ShowXYPlot"], payload) + warning and r.update({"warning": warning}) return r def show_chart(self, group_id=PINNED_CHART_VIEWER_ID, **chart_params): @@ -1124,25 +1334,33 @@ def show_chart(self, group_id=PINNED_CHART_VIEWER_ID, **chart_params): Status of the request, like {'success': True}. """ - chart_id = chart_params.get('chartId') if 'chartId' in chart_params else gen_item_id('Plotly') + chart_id = ( + chart_params.get("chartId") + if "chartId" in chart_params + else gen_item_id("Plotly") + ) warning = None if self.is_triview(): - warning = 'group_id unnecessary and ignored in triview mode' if group_id else None + warning = ( + "group_id unnecessary and ignored in triview mode" if group_id else None + ) group_id = FireflyClient.PINNED_CHART_VIEWER_ID - payload = {'chartId': chart_id, - 'groupId': group_id, - 'viewerId': group_id, - 'chartType': 'plot.ly', - 'closable': True} - - for item in ['data', 'layout']: + payload = { + "chartId": chart_id, + "groupId": group_id, + "viewerId": group_id, + "chartType": "plot.ly", + "closable": True, + } + + for item in ["data", "layout"]: (item in chart_params) and payload.update({item: chart_params.get(item)}) - r = self.dispatch(ACTION_DICT['ShowPlot'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowPlot"], payload) + warning and r.update({"warning": warning}) return r - def show_coverage(self, viewer_id=None, table_group='main'): + def show_coverage(self, viewer_id=None, table_group="main"): """ Show image coverage associated with the active table in the specified table group @@ -1159,14 +1377,18 @@ def show_coverage(self, viewer_id=None, table_group='main'): Status of the request, like {'success': True} """ if self.is_triview(): - return {'success': True, 'warning': 'show_coverage ignored in triview mode'} + return {"success": True, "warning": "show_coverage ignored in triview mode"} - view_type = 'coverImage' - cid = viewer_id if viewer_id else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) - payload = {'viewerType': LO_VIEW_DICT[view_type], 'cellId': cid} - return self.dispatch(ACTION_DICT['ShowCoverage'], payload) + view_type = "coverImage" + cid = ( + viewer_id + if viewer_id + else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) + ) + payload = {"viewerType": LO_VIEW_DICT[view_type], "cellId": cid} + return self.dispatch(ACTION_DICT["ShowCoverage"], payload) - def show_image_metadata(self, viewer_id=None, table_group='main'): + def show_image_metadata(self, viewer_id=None, table_group="main"): """ Show the image associated with the active (image metadata) table in the specified table group @@ -1183,16 +1405,31 @@ def show_image_metadata(self, viewer_id=None, table_group='main'): Status of the request, like {'success': True} """ if self.is_triview(): - return {'success': True, 'warning': 'show_image_metadata ignored in triview mode'} - - view_type = 'imageMeta' - cid = viewer_id if viewer_id else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) - payload = {'viewerType': LO_VIEW_DICT[view_type], 'cellId': cid} - - return self.dispatch(ACTION_DICT['ShowImageMetaData'], payload) - - def add_extension(self, ext_type, plot_id=None, title='', tool_tip='', - shortcut_key='', extension_id=None, image_src=None): + return { + "success": True, + "warning": "show_image_metadata ignored in triview mode", + } + + view_type = "imageMeta" + cid = ( + viewer_id + if viewer_id + else ("%s-%s" % (LO_VIEW_DICT[view_type], table_group)) + ) + payload = {"viewerType": LO_VIEW_DICT[view_type], "cellId": cid} + + return self.dispatch(ACTION_DICT["ShowImageMetaData"], payload) + + def add_extension( + self, + ext_type, + plot_id=None, + title="", + tool_tip="", + shortcut_key="", + extension_id=None, + image_src=None, + ): """ Add an extension to the plot. Extensions are context menus that allows you to extend what Firefly can do when certain actions happen. @@ -1226,17 +1463,22 @@ def add_extension(self, ext_type, plot_id=None, title='', tool_tip='', """ if not extension_id: - extension_id = gen_item_id('Extension') - payload = {'extension': { - 'id': extension_id, 'plotId': plot_id, - 'imageUrl': create_image_url(image_src) if image_src else None, - 'title': title, 'extType': ext_type, - 'toolTip': tool_tip, 'shortcutKey': shortcut_key} - } - return self.dispatch(ACTION_DICT['AddExtension'], payload) + extension_id = gen_item_id("Extension") + payload = { + "extension": { + "id": extension_id, + "plotId": plot_id, + "imageUrl": create_image_url(image_src) if image_src else None, + "title": title, + "extType": ext_type, + "toolTip": tool_tip, + "shortcutKey": shortcut_key, + } + } + return self.dispatch(ACTION_DICT["AddExtension"], payload) def table_highlight_callback(self, func, columns): - """ Set a user-defined callback for table highlights + """Set a user-defined callback for table highlights Parameters ---------- @@ -1258,15 +1500,16 @@ def table_highlight_callback(self, func, columns): ------- func : the callback function that was added """ + def highlight_callback(event): - if event['data'].get('type') == 'table.highlight': - absolute_row = int(event['data']['row']['ROW_IDX']) - relative_row = int(event['data']['row']['ROW_NUM']) - tbl_id = event.get('tbl_id') - col_data = copy(event['data']['row']) + if event["data"].get("type") == "table.highlight": + absolute_row = int(event["data"]["row"]["ROW_IDX"]) + relative_row = int(event["data"]["row"]["ROW_NUM"]) + tbl_id = event.get("tbl_id") + col_data = copy(event["data"]["row"]) if columns is not None: - col_data.pop('ROW_IDX') - col_data.pop('ROW_NUM') + col_data.pop("ROW_IDX") + col_data.pop("ROW_NUM") if len(columns) == 0: for k in col_data: col_data.pop(k) @@ -1274,12 +1517,19 @@ def highlight_callback(event): for k in columns: col_data.pop(k) func(absolute_row, relative_row, col_data, tbl_id) + self.add_listener(highlight_callback) - self.add_extension(ext_type='table.highlight', extension_id='table_highlight') + self.add_extension(ext_type="table.highlight", extension_id="table_highlight") return highlight_callback - def show_hips(self, plot_id=None, viewer_id=None, hips_root_url=None, hips_image_conversion=None, - **additional_params): + def show_hips( + self, + plot_id=None, + viewer_id=None, + hips_root_url=None, + hips_image_conversion=None, + **additional_params, + ): """ Show HiPS image. @@ -1314,33 +1564,45 @@ def show_hips(self, plot_id=None, viewer_id=None, hips_root_url=None, hips_image if not hips_root_url: return - wp_request = {'plotGroupId': 'groupFromPython', 'hipsRootUrl': hips_root_url} + wp_request = {"plotGroupId": "groupFromPython", "hipsRootUrl": hips_root_url} additional_params and wp_request.update(additional_params) - payload = {'wpRequest': wp_request} + payload = {"wpRequest": wp_request} if not plot_id: - plot_id = gen_item_id('Image') + plot_id = gen_item_id("Image") - payload.update({'plotId': plot_id}) - wp_request.update({'plotId': plot_id}) + payload.update({"plotId": plot_id}) + wp_request.update({"plotId": plot_id}) warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload.update({'viewerId': viewer_id}) + payload.update({"viewerId": viewer_id}) if hips_image_conversion and type(hips_image_conversion) is dict: - payload.update({'hipsImageConversion': hips_image_conversion}) + payload.update({"hipsImageConversion": hips_image_conversion}) - r = self.dispatch(ACTION_DICT['ShowHiPS'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowHiPS"], payload) + warning and r.update({"warning": warning}) return r - def show_image_or_hips(self, plot_id=None, viewer_id=None, image_request=None, hips_request=None, - fov_deg_fallover=0.12, allsky_request=None, plot_allsky_first=False): + def show_image_or_hips( + self, + plot_id=None, + viewer_id=None, + image_request=None, + hips_request=None, + fov_deg_fallover=0.12, + allsky_request=None, + plot_allsky_first=False, + ): """ Show a FiTS or HiPS image. @@ -1373,28 +1635,39 @@ def show_image_or_hips(self, plot_id=None, viewer_id=None, image_request=None, h return if not plot_id: - plot_id = gen_item_id('Image') + plot_id = gen_item_id("Image") warning = None if not viewer_id or self.is_triview(): - warning = 'viewer_id unnecessary and ignored in triview mode' if self.is_triview() and viewer_id else None + warning = ( + "viewer_id unnecessary and ignored in triview mode" + if self.is_triview() and viewer_id + else None + ) viewer_id = FireflyClient.PINNED_IMAGE_VIEWER_ID - payload = {'fovDegFallOver': fov_deg_fallover, 'plotAllSkyFirst': plot_allsky_first, - 'plotId': plot_id, 'viewerId': viewer_id} - - pg_key = 'plotGroupId' - if not ((hips_request and hips_request.get(pg_key)) or (image_request and image_request.get(pg_key))): + payload = { + "fovDegFallOver": fov_deg_fallover, + "plotAllSkyFirst": plot_allsky_first, + "plotId": plot_id, + "viewerId": viewer_id, + } + + pg_key = "plotGroupId" + if not ( + (hips_request and hips_request.get(pg_key)) + or (image_request and image_request.get(pg_key)) + ): if hips_request: - hips_request.update({pg_key: 'groupFromPython'}) + hips_request.update({pg_key: "groupFromPython"}) elif image_request: - image_request.update({pg_key: 'groupFromPython'}) + image_request.update({pg_key: "groupFromPython"}) - image_request and payload.update({'imageRequest': image_request}) - hips_request and payload.update({'hipsRequest': hips_request}) - allsky_request and payload.update({'allSkyRequest': allsky_request}) + image_request and payload.update({"imageRequest": image_request}) + hips_request and payload.update({"hipsRequest": hips_request}) + allsky_request and payload.update({"allSkyRequest": allsky_request}) - r = self.dispatch(ACTION_DICT['ShowImageOrHiPS'], payload) - warning and r.update({'warning': warning}) + r = self.dispatch(ACTION_DICT["ShowImageOrHiPS"], payload) + warning and r.update({"warning": warning}) return r # ---------------------------- @@ -1420,15 +1693,20 @@ def set_zoom(self, plot_id, factor=1.0): """ def zoom_oneplot(one_plot_id, f): - payload = {'plotId': one_plot_id, 'userZoomType': 'LEVEL', 'level': f, 'actionScope': 'SINGLE'} - return self.dispatch(ACTION_DICT['ZoomImage'], payload) + payload = { + "plotId": one_plot_id, + "userZoomType": "LEVEL", + "level": f, + "actionScope": "SINGLE", + } + return self.dispatch(ACTION_DICT["ZoomImage"], payload) if isinstance(plot_id, tuple) or isinstance(plot_id, list): return [zoom_oneplot(x, factor) for x in plot_id] else: return zoom_oneplot(plot_id, factor) - def set_pan(self, plot_id, x=None, y=None, coord='image'): + def set_pan(self, plot_id, x=None, y=None, coord="image"): """ Relocate the image to center on the given image coordinate or EQ_J2000 coordinate. If no (x, y) is given, the image is re-centered at the center of the image. @@ -1451,15 +1729,15 @@ def set_pan(self, plot_id, x=None, y=None, coord='image'): Status of the request, like {'success': True}. """ - payload = {'plotId': plot_id} - if coord.startswith('image'): - payload.update({'centerOnImage': 'true'}) + payload = {"plotId": plot_id} + if coord.startswith("image"): + payload.update({"centerOnImage": "true"}) elif x and y: - payload.update({'centerPt': f'{x};{y};{coord}'}) + payload.update({"centerPt": f"{x};{y};{coord}"}) - return self.dispatch(ACTION_DICT['PanImage'], payload) - - def align_images(self, match_type='Standard', lock_match=False): + return self.dispatch(ACTION_DICT["PanImage"], payload) + + def align_images(self, match_type="Standard", lock_match=False): """ Align the images being displayed. @@ -1477,12 +1755,14 @@ def align_images(self, match_type='Standard', lock_match=False): ------- out : `dict` Status of the request, like {'success': True}. - + """ payload = dict(matchType=match_type, lockMatch=lock_match) - return self.dispatch(ACTION_DICT['AlignImages'], payload) + return self.dispatch(ACTION_DICT["AlignImages"], payload) - def set_stretch(self, plot_id, stype=None, algorithm=None, band=None, **additional_params): + def set_stretch( + self, plot_id, stype=None, algorithm=None, band=None, **additional_params + ): """ Change the stretch of the image (no band or 3-color per-band cases). @@ -1528,27 +1808,35 @@ def set_stretch(self, plot_id, stype=None, algorithm=None, band=None, **addition `stype` is 'zscale', and `lower_value` and `upper_value` are used when `stype` is not 'zscale'. """ - serialized_rv = RangeValues.create_rv_by_stretch_type(algorithm, stype, **additional_params) - bands_3color = ['RED', 'GREEN', 'BLUE', 'ALL'] + serialized_rv = RangeValues.create_rv_by_stretch_type( + algorithm, stype, **additional_params + ) + bands_3color = ["RED", "GREEN", "BLUE", "ALL"] if not band: - band_list = ['NO_BAND'] + band_list = ["NO_BAND"] elif band in bands_3color: - band_list = ['RED', 'GREEN', 'BLUE'] if band == 'ALL' else [band] + band_list = ["RED", "GREEN", "BLUE"] if band == "ALL" else [band] else: - raise ValueError('invalid band: %s' % band) + raise ValueError("invalid band: %s" % band) st_data = [] for b in band_list: - st_data.append({'band': b, 'rv': serialized_rv, 'bandVisible': True}) + st_data.append({"band": b, "rv": serialized_rv, "bandVisible": True}) - payload = {'stretchData': st_data, 'plotId': plot_id} + payload = {"stretchData": st_data, "plotId": plot_id} - return_val = self.dispatch(ACTION_DICT['StretchImage'], payload) - return_val['rv_string'] = serialized_rv + return_val = self.dispatch(ACTION_DICT["StretchImage"], payload) + return_val["rv_string"] = serialized_rv return return_val - def set_stretch_hprgb(self, plot_id, asinh_q_value=None, scaling_k=1.0, - pedestal_value=1, pedestal_type='percent'): + def set_stretch_hprgb( + self, + plot_id, + asinh_q_value=None, + scaling_k=1.0, + pedestal_value=1, + pedestal_type="percent", + ): """ Change the stretch of RGB image (hue-preserving rgb case). When a parameter is a list, it must contain three elements, for red, green and blue bands respectively. @@ -1578,28 +1866,30 @@ def set_stretch_hprgb(self, plot_id, asinh_q_value=None, scaling_k=1.0, .. note:: `pedestal_value` is used when `pedestal_type` is not 'zscale'. """ - scaling_k = ensure3(scaling_k, 'scaling_k') - pedestal_type = ensure3(pedestal_type, 'pedestal_type') - pedestal_value = ensure3(pedestal_value, 'pedestal_value') + scaling_k = ensure3(scaling_k, "scaling_k") + pedestal_type = ensure3(pedestal_type, "pedestal_type") + pedestal_value = ensure3(pedestal_value, "pedestal_value") st_data = [] - bands = ['RED', 'GREEN', 'BLUE'] + bands = ["RED", "GREEN", "BLUE"] for i, band in enumerate(bands): - serialized_rv = RangeValues.create_rv(stretch_type=pedestal_type[i], - lower_value=pedestal_value[i], - upper_value=99.0, - algorithm='asinh', - asinh_q_value=asinh_q_value, - rgb_preserve_hue=1, - scaling_k=scaling_k[i]) - st_data.append({'band': band, 'rv': serialized_rv, 'bandVisible': True}) - - payload = {'stretchData': st_data, 'plotId': plot_id} - return_val = self.dispatch(ACTION_DICT['StretchImage'], payload) - return_val['rv_lst'] = [d['rv'] for d in st_data] + serialized_rv = RangeValues.create_rv( + stretch_type=pedestal_type[i], + lower_value=pedestal_value[i], + upper_value=99.0, + algorithm="asinh", + asinh_q_value=asinh_q_value, + rgb_preserve_hue=1, + scaling_k=scaling_k[i], + ) + st_data.append({"band": band, "rv": serialized_rv, "bandVisible": True}) + + payload = {"stretchData": st_data, "plotId": plot_id} + return_val = self.dispatch(ACTION_DICT["StretchImage"], payload) + return_val["rv_lst"] = [d["rv"] for d in st_data] return return_val - def set_color(self, plot_id, colormap_id=0, bias=.5, contrast=1): + def set_color(self, plot_id, colormap_id=0, bias=0.5, contrast=1): """ Change the color attributes (color map, bias, constrast) of an image plot. @@ -1624,15 +1914,23 @@ def set_color(self, plot_id, colormap_id=0, bias=.5, contrast=1): .. note:: when `colormap_id` is -1 for HiPS image, `contrast` and `bias` have no effect. """ - payload = {'plotId': plot_id, - 'cbarId': colormap_id, - 'bias': bias, - 'contrast': contrast - } - return self.dispatch(ACTION_DICT['ColorImage'], payload) - - def set_rgb_colors(self, plot_id, use_red=True, use_green=True, use_blue=True, - bias=[.5,.5,.5], contrast=[1,1,1]): + payload = { + "plotId": plot_id, + "cbarId": colormap_id, + "bias": bias, + "contrast": contrast, + } + return self.dispatch(ACTION_DICT["ColorImage"], payload) + + def set_rgb_colors( + self, + plot_id, + use_red=True, + use_green=True, + use_blue=True, + bias=[0.5, 0.5, 0.5], + contrast=[1, 1, 1], + ): """ Change the color attributes of a 3-color fits image plot. @@ -1656,14 +1954,15 @@ def set_rgb_colors(self, plot_id, use_red=True, use_green=True, use_blue=True, out : `dict` Status of the request, like {'success': True}. """ - payload = {'plotId': plot_id, - 'useRed': use_red, - 'useGreen': use_green, - 'useBlue': use_blue, - 'bias': bias, - 'contrast': contrast - } - return self.dispatch(ACTION_DICT['ColorImage'], payload) + payload = { + "plotId": plot_id, + "useRed": use_red, + "useGreen": use_green, + "useBlue": use_blue, + "bias": bias, + "contrast": contrast, + } + return self.dispatch(ACTION_DICT["ColorImage"], payload) @staticmethod def parse_rvstring(rvstring): @@ -1700,8 +1999,16 @@ def rvstring_from_dict(rvdict): # ----------------------------------------------------------------- # image line based footprint overlay # ----------------------------------------------------------------- - def overlay_footprints(self, footprint_file, footprint_image=None, title=None, - footprint_layer_id=None, plot_id=None, table_index=None, **additional_params): + def overlay_footprints( + self, + footprint_file, + footprint_image=None, + title=None, + footprint_layer_id=None, + plot_id=None, + table_index=None, + **additional_params, + ): """ Overlay a footprint dictionary on displayed images. The dictionary must be convertible to JSON format. @@ -1745,23 +2052,29 @@ def overlay_footprints(self, footprint_file, footprint_image=None, title=None, """ if not footprint_layer_id: - footprint_layer_id = gen_item_id('FootprintLayer') - payload = {'drawLayerId': footprint_layer_id} - - title and payload.update({'title': title}) - plot_id and payload.update({'plotId': plot_id}) - footprint_file and payload.update({'footprintFile': footprint_file}) - footprint_image and payload.update({'footprintImageFile': footprint_image}) - table_index and payload.update({'tbl_index': table_index}) + footprint_layer_id = gen_item_id("FootprintLayer") + payload = {"drawLayerId": footprint_layer_id} + + title and payload.update({"title": title}) + plot_id and payload.update({"plotId": plot_id}) + footprint_file and payload.update({"footprintFile": footprint_file}) + footprint_image and payload.update({"footprintImageFile": footprint_image}) + table_index and payload.update({"tbl_index": table_index}) additional_params and payload.update(additional_params) - return self.dispatch(ACTION_DICT['ImagelineBasedFootprint'], payload) + return self.dispatch(ACTION_DICT["ImagelineBasedFootprint"], payload) # ----------------------------------------------------------------- # Region Stuff # ----------------------------------------------------------------- - def overlay_region_layer(self, file_on_server=None, region_data=None, title=None, - region_layer_id=None, plot_id=None): + def overlay_region_layer( + self, + file_on_server=None, + region_data=None, + title=None, + region_layer_id=None, + plot_id=None, + ): """ Overlay a region layer on the loaded FITS images. The regions are defined either by a file or by text region description. @@ -1793,17 +2106,17 @@ def overlay_region_layer(self, file_on_server=None, region_data=None, title=None """ if not region_layer_id: - region_layer_id = gen_item_id('RegionLayer') - payload = {'drawLayerId': region_layer_id} + region_layer_id = gen_item_id("RegionLayer") + payload = {"drawLayerId": region_layer_id} - title and payload.update({'layerTitle': title}) - plot_id and payload.update({'plotId': plot_id}) + title and payload.update({"layerTitle": title}) + plot_id and payload.update({"plotId": plot_id}) if file_on_server: - payload.update({'fileOnServer': file_on_server}) + payload.update({"fileOnServer": file_on_server}) elif region_data: - payload.update({'regionAry': region_data}) + payload.update({"regionAry": region_data}) - return self.dispatch(ACTION_DICT['CreateRegionLayer'], payload) + return self.dispatch(ACTION_DICT["CreateRegionLayer"], payload) def delete_region_layer(self, region_layer_id, plot_id=None): """ @@ -1823,9 +2136,9 @@ def delete_region_layer(self, region_layer_id, plot_id=None): Status of the request, like {'success': True}. """ - payload = {'drawLayerId': region_layer_id} - plot_id and payload.update({'plotId': plot_id}) - return self.dispatch(ACTION_DICT['DeleteRegionLayer'], payload) + payload = {"drawLayerId": region_layer_id} + plot_id and payload.update({"plotId": plot_id}) + return self.dispatch(ACTION_DICT["DeleteRegionLayer"], payload) def add_region_data(self, region_data, region_layer_id, title=None, plot_id=None): """ @@ -1854,10 +2167,10 @@ def add_region_data(self, region_data, region_layer_id, title=None, plot_id=None """ - payload = {'regionChanges': region_data, 'drawLayerId': region_layer_id} - plot_id and payload.update({'plotId': plot_id}) - title and payload.update({'layerTitle': title}) - return self.dispatch(ACTION_DICT['AddRegionData'], payload) + payload = {"regionChanges": region_data, "drawLayerId": region_layer_id} + plot_id and payload.update({"plotId": plot_id}) + title and payload.update({"layerTitle": title}) + return self.dispatch(ACTION_DICT["AddRegionData"], payload) def remove_region_data(self, region_data, region_layer_id): """ @@ -1875,12 +2188,20 @@ def remove_region_data(self, region_data, region_layer_id): out : `dict` Status of the request, like {'success': True}. """ - payload = {'regionChanges': region_data, 'drawLayerId': region_layer_id} + payload = {"regionChanges": region_data, "drawLayerId": region_layer_id} - return self.dispatch(ACTION_DICT['RemoveRegionData'], payload) + return self.dispatch(ACTION_DICT["RemoveRegionData"], payload) - def add_mask(self, bit_number, image_number, plot_id, mask_id=None, color=None, title=None, - file_on_server=None): + def add_mask( + self, + bit_number, + image_number, + plot_id, + mask_id=None, + color=None, + title=None, + file_on_server=None, + ): """ Add a mask layer. @@ -1909,15 +2230,21 @@ def add_mask(self, bit_number, image_number, plot_id, mask_id=None, color=None, """ if not mask_id: - mask_id = gen_item_id('MaskLayer') + mask_id = gen_item_id("MaskLayer") if not title: - title = 'bit %23 ' + str(bit_number) - - payload = {'plotId': plot_id, 'imageOverlayId': mask_id, 'imageNumber': image_number, - 'maskNumber': bit_number, 'maskValue': int(math.pow(2, bit_number)), 'title': title} - color and payload.update({'color': color}) - file_on_server and payload.update({'fileKey': file_on_server}) - return self.dispatch(ACTION_DICT['PlotMask'], payload) + title = "bit %23 " + str(bit_number) + + payload = { + "plotId": plot_id, + "imageOverlayId": mask_id, + "imageNumber": image_number, + "maskNumber": bit_number, + "maskValue": int(math.pow(2, bit_number)), + "title": title, + } + color and payload.update({"color": color}) + file_on_server and payload.update({"fileKey": file_on_server}) + return self.dispatch(ACTION_DICT["PlotMask"], payload) def remove_mask(self, plot_id, mask_id): """ @@ -1936,8 +2263,8 @@ def remove_mask(self, plot_id, mask_id): Status of the request, like {'success': True} """ - payload = {'plotId': plot_id, 'imageOverlayId': mask_id} - return self.dispatch(ACTION_DICT['DeleteOverlayMask'], payload) + payload = {"plotId": plot_id, "imageOverlayId": mask_id} + return self.dispatch(ACTION_DICT["DeleteOverlayMask"], payload) # ---------------------------- # actions on table @@ -1960,11 +2287,11 @@ def apply_table_filters(self, tbl_id, filters): out : `dict` Status of the request, like {'success': True} """ - tbl_req = {'tbl_id': tbl_id, 'filters': filters} - payload = {'request': tbl_req} - return self.dispatch(ACTION_DICT['TableFilter'], payload) - - def sort_table_column(self, tbl_id, column_name, sort_direction=''): + tbl_req = {"tbl_id": tbl_id, "filters": filters} + payload = {"request": tbl_req} + return self.dispatch(ACTION_DICT["TableFilter"], payload) + + def sort_table_column(self, tbl_id, column_name, sort_direction=""): """ Sort a loaded table by a given column name. @@ -1975,7 +2302,7 @@ def sort_table_column(self, tbl_id, column_name, sort_direction=''): column_name : `str` Name of the table column to sort sort_direction : {'', 'ASC', 'DESC'}, optional - Direction of sort: '' for unsorted (or for removing the sort), + Direction of sort: '' for unsorted (or for removing the sort), 'ASC' for ascending, and 'DESC' for descending. Default is ''. Returns @@ -1983,10 +2310,12 @@ def sort_table_column(self, tbl_id, column_name, sort_direction=''): out : `dict` Status of the request, like {'success': True} """ - sort_directions = ['', 'ASC', 'DESC'] + sort_directions = ["", "ASC", "DESC"] if sort_direction not in sort_directions: - raise ValueError(f'Invalid sort_direction. Valid values are {sort_directions}') - - tbl_req = {'tbl_id': tbl_id, 'sortInfo': f'{sort_direction},{column_name}'} - payload = {'request': tbl_req} - return self.dispatch(ACTION_DICT['TableSort'], payload) + raise ValueError( + f"Invalid sort_direction. Valid values are {sort_directions}" + ) + + tbl_req = {"tbl_id": tbl_id, "sortInfo": f"{sort_direction},{column_name}"} + payload = {"request": tbl_req} + return self.dispatch(ACTION_DICT["TableSort"], payload) diff --git a/firefly_client/plot.py b/firefly_client/plot.py index bd2dc04..1d90be1 100644 --- a/firefly_client/plot.py +++ b/firefly_client/plot.py @@ -10,9 +10,9 @@ import logging import os import tempfile -import time -from .firefly_client import FireflyClient + from .fc_utils import gen_item_id +from .firefly_client import FireflyClient logger = logging.getLogger(__name__) @@ -20,9 +20,9 @@ last_tblid = None last_imageid = None # Set up default cells -table_cellid = 'tables' -plots_cellid = 'plots' -images_cellid = 'images' +table_cellid = "tables" +plots_cellid = "plots" +images_cellid = "images" def use_client(ffclient): @@ -42,7 +42,9 @@ def use_client(ffclient): def _confirm_fc(): if fc is None: - raise ValueError('a FireflyClient instance has not been defined, define it first with use_client()') + raise ValueError( + "a FireflyClient instance has not been defined, define it first with use_client()" + ) def reset_layout(): @@ -52,15 +54,22 @@ def reset_layout(): row containing plots in the first column and images in the second column. """ _confirm_fc() - fc.add_cell(row=0, col=0, width=2, height=2, element_type='tables', cell_id=table_cellid) - fc.add_cell(row=2, col=0, width=1, height=2, element_type='xyPlots', cell_id=plots_cellid) - fc.add_cell(row=2, col=1, width=1, height=2, element_type='images', cell_id=images_cellid) + assert isinstance(fc, FireflyClient) + fc.add_cell( + row=0, col=0, width=2, height=2, element_type="tables", cell_id=table_cellid + ) + fc.add_cell( + row=2, col=0, width=1, height=2, element_type="xyPlots", cell_id=plots_cellid + ) + fc.add_cell( + row=2, col=1, width=1, height=2, element_type="images", cell_id=images_cellid + ) def display_url(): - """Display the web viewer URL, if possible as a clickable link - """ + """Display the web viewer URL, if possible as a clickable link""" _confirm_fc() + assert isinstance(fc, FireflyClient) fc.display_url() @@ -73,18 +82,30 @@ def open_browser(force=False): if True, open the browser even if one is already connected. Default False """ _confirm_fc() + assert isinstance(fc, FireflyClient) fc.launch_browser(force=force) def clear(): - """Clear the web viewer - """ + """Clear the web viewer""" _confirm_fc() + assert isinstance(fc, FireflyClient) fc.reinit_viewer() -def scatter(x_col, y_col, tbl_id='', size=4, color=None, alpha=1.0, - title='', xlabel=None, ylabel=None, cell_id=plots_cellid, **kwargs): +def scatter( + x_col, + y_col, + tbl_id="", + size=4, + color=None, + alpha=1.0, + title="", + xlabel=None, + ylabel=None, + cell_id=plots_cellid, + **kwargs, +): """Make a scatter plot from a table uploaded to Firefly Parameters: @@ -114,24 +135,41 @@ def scatter(x_col, y_col, tbl_id='', size=4, color=None, alpha=1.0, """ _confirm_fc() - layout = dict(xaxis=dict(title=xlabel if xlabel else x_col), - yaxis=dict(title=ylabel if ylabel else y_col)) + assert isinstance(fc, FireflyClient) + layout: dict[str, dict[str, str | int] | str] = dict( + xaxis=dict(title=xlabel if xlabel else x_col), + yaxis=dict(title=ylabel if ylabel else y_col), + ) if title is not None: - layout['title'] = title + layout["title"] = title if len(tbl_id) == 0: - logger.debug('Using last_tblid: {}'.format(last_tblid)) + logger.debug("Using last_tblid: {}".format(last_tblid)) tbl_id = last_tblid marker_dict = dict(size=size, opacity=alpha) if color: - marker_dict['color'] = color - trace = dict(tbl_id=tbl_id, x='tables::' + x_col, y='tables::' + y_col, - mode='markers', type='scatter', marker=marker_dict) + marker_dict["color"] = color + trace = dict( + tbl_id=tbl_id, + x="tables::" + x_col, + y="tables::" + y_col, + mode="markers", + type="scatter", + marker=marker_dict, + ) trace.update(kwargs) fc.show_chart(layout=layout, data=[trace], group_id=cell_id) -def hist(data_col, tbl_id='', nbins=30, title='', xlabel=None, - ylabel=None, cell_id=plots_cellid, **kwargs): +def hist( + data_col, + tbl_id="", + nbins=30, + title="", + xlabel=None, + ylabel=None, + cell_id=plots_cellid, + **kwargs, +): """Make a histogram from a table uploaded to Firefly data_col: `str` @@ -150,34 +188,44 @@ def hist(data_col, tbl_id='', nbins=30, title='', xlabel=None, ID of Slate cell from add_cell, defaults to table_cellid """ _confirm_fc() - layout = dict(xaxis=dict(title=xlabel if xlabel else data_col), - yaxis=dict(title=ylabel if ylabel else 'Number')) + assert isinstance(fc, FireflyClient) + layout: dict[str, dict[str, str | int] | str] = dict( + xaxis=dict(title=xlabel if xlabel else data_col), + yaxis=dict(title=ylabel if ylabel else "Number"), + ) if title is not None: - layout['title'] = title + layout["title"] = title if len(tbl_id) == 0: - logger.debug('Using last_tblid: {}'.format(last_tblid)) + logger.debug("Using last_tblid: {}".format(last_tblid)) tbl_id = last_tblid hist_data = dict( - type='fireflyHistogram', + type="fireflyHistogram", name=data_col, - marker={'color': 'rgba(153, 51, 153, 0.8)'}, + marker={"color": "rgba(153, 51, 153, 0.8)"}, firefly=dict( tbl_id=tbl_id, options=dict( - algorithm='fixedSizeBins', - fixedBinSizeSelection='numBins', + algorithm="fixedSizeBins", + fixedBinSizeSelection="numBins", numBins=nbins, - columnOrExpr=data_col - ) - ) + columnOrExpr=data_col, + ), + ), ) hist_data.update(kwargs) fc.show_chart(group_id=cell_id, layout=layout, data=[hist_data]) return -def upload_table(table, title=None, show=True, write_func="auto", tbl_index=1, page_size=200, - view_coverage=False): +def upload_table( + table, + title=None, + show=True, + write_func="auto", + tbl_index=1, + page_size=200, + view_coverage=False, +): """upload a table object to Firefly Parameters: @@ -202,50 +250,61 @@ def upload_table(table, title=None, show=True, write_func="auto", tbl_index=1, p Table ID on the Firefly server """ _confirm_fc() + assert isinstance(fc, FireflyClient) if isinstance(table, str): tval = fc.upload_file(table) else: - if write_func == 'auto': - if 'lsst.afw.table' in str(type(table)): + if write_func == "auto": + if "lsst.afw.table" in str(type(table)): write_func = table.writeFits - elif 'astropy.table.table.Table' in str(type(table)): + elif "astropy.table.table.Table" in str(type(table)): + def write_astropy(fname): atable = table.copy() for c in atable.colnames: if len(c) > 68: atable.rename_column(c, c[:68]) import warnings + from astropy.utils.exceptions import AstropyWarning + with warnings.catch_warnings(): - warnings.simplefilter('ignore', AstropyWarning) - atable.write(fname, format='fits', overwrite=True) + warnings.simplefilter("ignore", AstropyWarning) + atable.write(fname, format="fits", overwrite=True) + write_func = write_astropy else: - raise RuntimeError('Unable to auto-discover output method for ' + str(type(table))) - with tempfile.NamedTemporaryFile(delete=False, suffix='.fits') as fd: + raise RuntimeError( + "Unable to auto-discover output method for " + str(type(table)) + ) + with tempfile.NamedTemporaryFile(delete=False, suffix=".fits") as fd: write_func(fd.name) tval = fc.upload_file(fd.name) - logger.debug('Image name is {}'.format(fd.name)) + logger.debug("Image name is {}".format(fd.name)) os.remove(fd.name) if title is None: - tbl_id = gen_item_id('Table') + tbl_id = gen_item_id("Table") else: tbl_id = title if view_coverage: coverage() if show: - status = fc.show_table(tval, tbl_id=tbl_id, title=title, table_index=tbl_index, page_size=page_size) + status = fc.show_table( + tval, tbl_id=tbl_id, title=title, table_index=tbl_index, page_size=page_size + ) else: - status = fc.fetch_table(tval, tbl_id=tbl_id, table_index=tbl_index, page_size=page_size) - if status['success']: + status = fc.fetch_table( + tval, tbl_id=tbl_id, table_index=tbl_index, page_size=page_size + ) + if status["success"]: global last_tblid last_tblid = tbl_id return tbl_id else: - raise RuntimeError('table upload unsuccessful') + raise RuntimeError("table upload unsuccessful") -def upload_image(image, title=None, write_func='auto', cell_id=images_cellid): +def upload_image(image, title=None, write_func="auto", cell_id=images_cellid): """display an array or astropy.io.fits HDU or HDUList Parameters: @@ -266,40 +325,47 @@ def upload_image(image, title=None, write_func='auto', cell_id=images_cellid): if isinstance(image, str): fval = fc.upload_file(image) else: - if write_func == 'auto': - if 'lsst.afw.image' in str(type(image)): + if write_func == "auto": + if "lsst.afw.image" in str(type(image)): write_func = image.writeFits - elif 'astropy.io.fits' in str(type(image)): + elif "astropy.io.fits" in str(type(image)): + def write_astropy_image(fname): import warnings + from astropy.utils.exceptions import AstropyWarning + with warnings.catch_warnings(): - warnings.simplefilter('ignore', AstropyWarning) + warnings.simplefilter("ignore", AstropyWarning) image.writeto(fname, overwrite=True) + write_func = write_astropy_image - elif 'numpy.ndarray' in str(type(image)): + elif "numpy.ndarray" in str(type(image)): import astropy.io.fits + hdu = astropy.io.fits.PrimaryHDU(data=image) write_func = lambda fname: hdu.writeto(fname, overwrite=True) else: - raise RuntimeError('Unable to auto-discover output method for ' + str(type(table))) - with tempfile.NamedTemporaryFile(delete=False, suffix='.fits') as fd: + raise RuntimeError( + "Unable to auto-discover output method for " + str(type(table)) + ) + with tempfile.NamedTemporaryFile(delete=False, suffix=".fits") as fd: write_func(fd.name) fval = fc.upload_file(fd.name) - logger.debug('Image name is {}'.format(fd.name)) + logger.debug("Image name is {}".format(fd.name)) os.remove(fd.name) if title is None: - image_id = gen_item_id('Image') + image_id = gen_item_id("Image") title = image_id else: image_id = title status = fc.show_fits(fval, plot_id=image_id, title=title, viewer_id=cell_id) - if status['success']: + if status["success"]: global last_imageid last_imageid = image_id return image_id else: - raise RuntimeError('image upload and display unsuccessful') + raise RuntimeError("image upload and display unsuccessful") def coverage(cell_id=images_cellid): diff --git a/firefly_client/range_values.py b/firefly_client/range_values.py index b3beb9a..8883a15 100644 --- a/firefly_client/range_values.py +++ b/firefly_client/range_values.py @@ -1,69 +1,119 @@ import math +from typing import ClassVar class RangeValues: # for serializing the RangeValues object - STRETCH_TYPE_DICT = {'percent': 88, 'minmax': 89, 'absolute': 90, 'zscale': 91, 'sigma': 92} + STRETCH_TYPE_DICT: ClassVar = { + "percent": 88, + "minmax": 89, + "absolute": 90, + "zscale": 91, + "sigma": 92, + } """Definition of stretch type (`dict`).""" - INVERSE_STRETCH_TYPE = {v: k for k, v in STRETCH_TYPE_DICT.items()} + INVERSE_STRETCH_TYPE: ClassVar = {v: k for k, v in STRETCH_TYPE_DICT.items()} - STRETCH_ALGORITHM_DICT = {'linear': 44, 'log': 45, 'loglog': 46, 'equal': 47, 'squared': 48, 'sqrt': 49, - 'asinh': 50, 'powerlaw_gamma': 51} + STRETCH_ALGORITHM_DICT: ClassVar = { + "linear": 44, + "log": 45, + "loglog": 46, + "equal": 47, + "squared": 48, + "sqrt": 49, + "asinh": 50, + "powerlaw_gamma": 51, + } """Definition of stretch algorithm (`dict`).""" - INVERSE_STRETCH_ALGORITHM = {v: k for k, v in STRETCH_ALGORITHM_DICT.items()} + INVERSE_STRETCH_ALGORITHM: ClassVar = { + v: k for k, v in STRETCH_ALGORITHM_DICT.items() + } @classmethod - def create_rv(cls, stretch_type, lower_value, upper_value, algorithm, - zscale_contrast=25, zscale_samples=600, zscale_samples_perline=120, - asinh_q_value=None, gamma_value=2.0, - rgb_preserve_hue=0, asinh_stretch=None, scaling_k=1.0): + def create_rv( + cls, + stretch_type, + lower_value, + upper_value, + algorithm, + zscale_contrast=25, + zscale_samples=600, + zscale_samples_perline=120, + asinh_q_value=None, + gamma_value=2.0, + rgb_preserve_hue=0, + asinh_stretch=None, + scaling_k=1.0, + ): retval = None st = stretch_type.lower() a = algorithm.lower() # when q is NaN (case-sensitive), Firefly will calculate q using range if asinh_q_value is None or math.isnan(asinh_q_value): - qstr = 'NaN' + qstr = "NaN" elif math.isinf(asinh_q_value): - raise ValueError('invalid asinh_q_value: %f' % asinh_q_value) + raise ValueError("invalid asinh_q_value: %f" % asinh_q_value) else: - qstr = '%f' % asinh_q_value + qstr = "%f" % asinh_q_value # when asinh_stretch is NaN (case-sensitive), Firefly will calculate asinh_stretch # for hue-preserving rgb using z-scale range of intensity if asinh_stretch is None or math.isnan(asinh_stretch): - asinh_stretch_str = 'NaN' + asinh_stretch_str = "NaN" elif math.isinf(asinh_stretch) or asinh_stretch < 0: - raise ValueError('invalid asinh_stretch for hue-preserving rgb: %f' % asinh_stretch) + raise ValueError( + "invalid asinh_stretch for hue-preserving rgb: %f" % asinh_stretch + ) else: - asinh_stretch_str = '%f' % asinh_stretch + asinh_stretch_str = "%f" % asinh_stretch if rgb_preserve_hue is None: rgb_preserve_hue = 0 if st in cls.STRETCH_TYPE_DICT and a in cls.STRETCH_ALGORITHM_DICT: - retval = '%d,%f,%d,%f,%s,%f,%d,%d,%d,%d,%d,%s,%f' % \ - (cls.STRETCH_TYPE_DICT[st], lower_value, - cls.STRETCH_TYPE_DICT[st], upper_value, - qstr, gamma_value, - cls.STRETCH_ALGORITHM_DICT[a], - zscale_contrast, zscale_samples, zscale_samples_perline, - rgb_preserve_hue, asinh_stretch_str, scaling_k) + retval = "%d,%f,%d,%f,%s,%f,%d,%d,%d,%d,%d,%s,%f" % ( + cls.STRETCH_TYPE_DICT[st], + lower_value, + cls.STRETCH_TYPE_DICT[st], + upper_value, + qstr, + gamma_value, + cls.STRETCH_ALGORITHM_DICT[a], + zscale_contrast, + zscale_samples, + zscale_samples_perline, + rgb_preserve_hue, + asinh_stretch_str, + scaling_k, + ) return retval @classmethod def create_rv_by_stretch_type(cls, algorithm, stretch_type, **additional_params): - a = algorithm.lower() if algorithm else 'linear' - st = stretch_type.lower() if stretch_type else 'percent' - if st == 'zscale': + a = algorithm.lower() if algorithm else "linear" + st = stretch_type.lower() if stretch_type else "percent" + if st == "zscale": return cls.create_rv_zscale(a, **additional_params) - elif st in ['minmax', 'maxmin']: # 'maxmin' retained for backwards compatibility - return cls.create_rv_standard(a, 'percent', lower_value=0, upper_value=100, **additional_params) + elif st in [ + "minmax", + "maxmin", + ]: # 'maxmin' retained for backwards compatibility + return cls.create_rv_standard( + a, "percent", lower_value=0, upper_value=100, **additional_params + ) else: return cls.create_rv_standard(a, stretch_type, **additional_params) @classmethod - def create_rv_standard(cls, algorithm, stretch_type='Percent', lower_value=1, upper_value=99, **additional_params): + def create_rv_standard( + cls, + algorithm, + stretch_type="Percent", + lower_value=1, + upper_value=99, + **additional_params, + ): """ Create range values for non-zscale cases. @@ -93,16 +143,32 @@ def create_rv_standard(cls, algorithm, stretch_type='Percent', lower_value=1, up out : `str` a serialized range values string """ - retval = cls.create_rv(stretch_type, lower_value, upper_value, algorithm, **additional_params) + retval = cls.create_rv( + stretch_type, lower_value, upper_value, algorithm, **additional_params + ) if not retval: - t = stretch_type if stretch_type.lower() in cls.STRETCH_TYPE_DICT else 'percent' - a = algorithm if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT else 'linear' + t = ( + stretch_type + if stretch_type.lower() in cls.STRETCH_TYPE_DICT + else "percent" + ) + a = ( + algorithm + if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT + else "linear" + ) retval = cls.create_rv(t, 1, 99, a, **additional_params) return retval @classmethod - def create_rv_zscale(cls, algorithm, zscale_contrast=25, - zscale_samples=600, zscale_samples_perline=120, **additional_params): + def create_rv_zscale( + cls, + algorithm, + zscale_contrast=25, + zscale_samples=600, + zscale_samples_perline=120, + **additional_params, + ): """ Create range values for zscale case. @@ -132,11 +198,23 @@ def create_rv_zscale(cls, algorithm, zscale_contrast=25, out : `str` a serialized range values string """ - retval = RangeValues.create_rv('zscale', 1, 1, algorithm, zscale_contrast, zscale_samples, - zscale_samples_perline, **additional_params) + retval = RangeValues.create_rv( + "zscale", + 1, + 1, + algorithm, + zscale_contrast, + zscale_samples, + zscale_samples_perline, + **additional_params, + ) if not retval: - a = algorithm if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT else 'linear' - retval = cls.create_rv('zscale', 1, 2, a, 25, 600, 120, **additional_params) + a = ( + algorithm + if algorithm.lower() in cls.STRETCH_ALGORITHM_DICT + else "linear" + ) + retval = cls.create_rv("zscale", 1, 2, a, 25, 600, 120, **additional_params) return retval @classmethod @@ -153,22 +231,24 @@ def parse_rvstring(cls, rvstring): outdict : `dict` dictionary of the inputs """ - vals = rvstring.split(',') + vals = rvstring.split(",") assert 10 <= len(vals) <= 13 - outdict = dict(lower_type=cls.INVERSE_STRETCH_TYPE[int(vals[0])], - lower_value=float(vals[1]), - upper_type=cls.INVERSE_STRETCH_TYPE[int(vals[2])], - upper_value=float(vals[3]), - asinh_q_value=float(vals[4]), - gamma_value=float(vals[5]), - algorithm=cls.INVERSE_STRETCH_ALGORITHM[int(vals[6])], - zscale_contrast=int(vals[7]), - zscale_samples=int(vals[8]), - zscale_samples_perline=int(vals[9])) + outdict = dict( + lower_type=cls.INVERSE_STRETCH_TYPE[int(vals[0])], + lower_value=float(vals[1]), + upper_type=cls.INVERSE_STRETCH_TYPE[int(vals[2])], + upper_value=float(vals[3]), + asinh_q_value=float(vals[4]), + gamma_value=float(vals[5]), + algorithm=cls.INVERSE_STRETCH_ALGORITHM[int(vals[6])], + zscale_contrast=int(vals[7]), + zscale_samples=int(vals[8]), + zscale_samples_perline=int(vals[9]), + ) if len(vals) > 10: - outdict['rgb_preserve_hue'] = int(vals[10]) - outdict['asinh_stretch'] = float(vals[11]) - outdict['scaling_k'] = float(vals[12]) + outdict["rgb_preserve_hue"] = int(vals[10]) + outdict["asinh_stretch"] = float(vals[11]) + outdict["scaling_k"] = float(vals[12]) return outdict @classmethod @@ -186,8 +266,19 @@ def rvstring_from_dict(cls, rvdict): RangeValues string that can be passed to the show_fits methods """ - argnames = ['lower_value', 'upper_value', 'upper_value', 'algorithm', - 'zscale_contrast', 'zscale_samples', 'zscale_samples_perline', - 'asinh_q_value', 'gamma_value', 'rgb_preserve_hue', 'asinh_stretch', 'scaling_k'] + argnames = [ + "lower_value", + "upper_value", + "upper_value", + "algorithm", + "zscale_contrast", + "zscale_samples", + "zscale_samples_perline", + "asinh_q_value", + "gamma_value", + "rgb_preserve_hue", + "asinh_stretch", + "scaling_k", + ] kw = dict((k, rvdict[k]) for k in argnames) - return RangeValues.create_rv(stretch_type=rvdict['lower_type'], **kw) + return RangeValues.create_rv(stretch_type=rvdict["lower_type"], **kw) diff --git a/pyproject.toml b/pyproject.toml index 115c0e5..fca4207 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,11 @@ authors = [{ name = "IPAC LSST SUIT" }] readme = "README.md" license = { file = "License.txt" } requires-python = ">=3.10" -dependencies = ["websocket-client", "requests"] +dependencies = [ + "websocket-client", + "requests", + "astropy>=6.1.7", +] keywords = [ "jupyter", "firefly", diff --git a/test/cb.ipynb b/test/cb.ipynb index 23735ca..a4d5601 100644 --- a/test/cb.ipynb +++ b/test/cb.ipynb @@ -8,7 +8,8 @@ "source": [ "import firefly_client\n", "from firefly_client import FireflyClient\n", - "firefly_client.__dict__['__version__'] " + "\n", + "firefly_client.__dict__[\"__version__\"]" ] }, { @@ -28,7 +29,9 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\")" + "fc.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\"\n", + ")" ] }, { @@ -39,9 +42,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" + "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\")\n", + "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -52,26 +55,26 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", " if False:\n", " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", + " if \"data\" not in ev:\n", + " print(\"no data found in ev\")\n", " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + " data = ev[\"data\"]\n", + " if \"payload\" in data:\n", + " print(data[\"payload\"])\n", + " if \"type\" in data:\n", + " print(data[\"type\"])\n", + " if data[\"type\"] == \"POINT\":\n", + " print(\" image point: %s\" % data[\"ipt\"])\n", + " print(\" world point: %s\" % data[\"wpt\"])\n", + " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", + " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", + " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", "\n", "\n", - " \n", "fc.add_listener(listener1)" ] }, diff --git a/test/test-simple-callback-in-lab.ipynb b/test/test-simple-callback-in-lab.ipynb index eb22147..f0b7f84 100644 --- a/test/test-simple-callback-in-lab.ipynb +++ b/test/test-simple-callback-in-lab.ipynb @@ -18,18 +18,20 @@ "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", "\n", "# some host\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", - "local_host = 'http://127.0.0.1:8080/firefly'\n", - "irsa = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", - "fd = 'https://fireflydev.ipac.caltech.edu/firefly'\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", + "irsa = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", + "fd = \"https://fireflydev.ipac.caltech.edu/firefly\"\n", "\n", - "#host = 'http://127.0.0.1:8080/suit'\n", + "# host = 'http://127.0.0.1:8080/suit'\n", "host = local_host\n", - "channel1 = 'channel-test-1'\n", + "channel1 = \"channel-test-1\"\n", "FireflyClient._debug = False\n", - "#fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", - "fc = FireflyClient.make_lab_client(start_browser_tab=False, start_tab=True, verbose=True )\n", - "#fc = FireflyClient.make_lab_client(start_tab=False)\n", + "# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", + "fc = FireflyClient.make_lab_client(\n", + " start_browser_tab=False, start_tab=True, verbose=True\n", + ")\n", + "# fc = FireflyClient.make_lab_client(start_tab=False)\n", "fc.get_firefly_url()" ] }, @@ -48,7 +50,10 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\", plot_id='x2')" + "fc.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\",\n", + " plot_id=\"x2\",\n", + ")" ] }, { @@ -57,7 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.set_stretch('x1', stype='zscale')" + "fc.set_stretch(\"x1\", stype=\"zscale\")" ] }, { @@ -68,9 +73,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line', shortcut_key='meta-e')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" + "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\", shortcut_key=\"meta-e\")\n", + "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -81,26 +86,26 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", " if False:\n", " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", + " if \"data\" not in ev:\n", + " print(\"no data found in ev\")\n", " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + " data = ev[\"data\"]\n", + " if \"payload\" in data:\n", + " print(data[\"payload\"])\n", + " if \"type\" in data:\n", + " print(data[\"type\"])\n", + " if data[\"type\"] == \"POINT\":\n", + " print(\" image point: %s\" % data[\"ipt\"])\n", + " print(\" world point: %s\" % data[\"wpt\"])\n", + " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", + " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", + " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", "\n", "\n", - " \n", "fc.add_listener(listener1)" ] }, @@ -111,6 +116,7 @@ "outputs": [], "source": [ "from firefly_client import __version__ as v\n", + "\n", "v" ] }, diff --git a/test/test_basic_tableload.py b/test/test_basic_tableload.py new file mode 100644 index 0000000..09476ef --- /dev/null +++ b/test/test_basic_tableload.py @@ -0,0 +1,44 @@ +import os + +from firefly_client import FireflyClient +from pytest_container.container import Container, ContainerData, EntrypointSelection +from pytest_container.inspect import NetworkProtocol, PortForwarding + +FIREFLY_CONTAINER = Container( + url="docker.io/irsa/firefly:latest", + extra_launch_args=["--memory=4g"], + singleton=True, + entry_point=EntrypointSelection.AUTO, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=8080, + bind_ip="127.0.0.1", + ) + ], +) + +CONTAINER_IMAGES = [FIREFLY_CONTAINER] + + +def test_tableload(auto_container: ContainerData): + assert auto_container.forwarded_ports[0].host_port == 8080 + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + fc = FireflyClient.make_client(host) + + testdata_repo_path = "/hydra/cm" + + localfile = os.path.join(testdata_repo_path, "MF.20210502.18830.fits") + filename = fc.upload_file(localfile) + + fc.show_table(filename) + + fc.show_table(filename, table_index=2) + + localfile = os.path.join(testdata_repo_path, "Mr31objsearch.xml") + filename = fc.upload_file(localfile) + + fc.show_table(filename, tbl_id="votable-0") + + fc.show_table(filename, tbl_id="votable-1", table_index=1) diff --git a/test/test_simple_callback_in_lab.ipynb b/test/test_simple_callback_in_lab.ipynb index fc87614..5cd989e 100644 --- a/test/test_simple_callback_in_lab.ipynb +++ b/test/test_simple_callback_in_lab.ipynb @@ -18,18 +18,20 @@ "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", "\n", "# some host\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", - "local_host = 'http://127.0.0.1:8080/firefly'\n", - "irsa = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", - "fd = 'https://fireflydev.ipac.caltech.edu/firefly'\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", + "irsa = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", + "fd = \"https://fireflydev.ipac.caltech.edu/firefly\"\n", "\n", - "#host = 'http://127.0.0.1:8080/suit'\n", + "# host = 'http://127.0.0.1:8080/suit'\n", "host = local_host\n", - "channel1 = 'channel-test-1'\n", + "channel1 = \"channel-test-1\"\n", "FireflyClient._debug = False\n", - "#fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", - "fc = FireflyClient.make_lab_client(start_browser_tab=False, start_tab=True, verbose=True )\n", - "#fc = FireflyClient.make_lab_client(start_tab=False)\n", + "# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", + "fc = FireflyClient.make_lab_client(\n", + " start_browser_tab=False, start_tab=True, verbose=True\n", + ")\n", + "# fc = FireflyClient.make_lab_client(start_tab=False)\n", "fc.get_firefly_url()" ] }, @@ -48,7 +50,10 @@ "metadata": {}, "outputs": [], "source": [ - "fc.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\", plot_id='x2')" + "fc.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\",\n", + " plot_id=\"x2\",\n", + ")" ] }, { @@ -57,7 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "fc.set_stretch('x1', stype='zscale')" + "fc.set_stretch(\"x1\", stype=\"zscale\")" ] }, { @@ -68,9 +73,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc.add_extension(ext_type='LINE_SELECT', title='a line', shortcut_key='meta-e')\n", - "fc.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc.add_extension(ext_type='POINT', title='a point')" + "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\", shortcut_key=\"meta-e\")\n", + "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -81,26 +86,26 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", " if False:\n", " print(ev)\n", - " if 'data' not in ev:\n", - " print('no data found in ev')\n", + " if \"data\" not in ev:\n", + " print(\"no data found in ev\")\n", " return\n", - " data = ev['data']\n", - " if 'payload' in data:\n", - " print(data['payload'])\n", - " if 'type' in data:\n", - " print(data['type'])\n", - " if data['type'] == 'POINT':\n", - " print(' image point: %s' % data['ipt'])\n", - " print(' world point: %s' % data['wpt'])\n", - " if data['type'] == 'LINE_SELECT' or data['type'] == 'AREA_SELECT':\n", - " print(' image points: %s to %s' % (data['ipt0'], data['ipt1']))\n", - " print(' world points: %s to %s' % (data['wpt0'], data['wpt1']))\n", + " data = ev[\"data\"]\n", + " if \"payload\" in data:\n", + " print(data[\"payload\"])\n", + " if \"type\" in data:\n", + " print(data[\"type\"])\n", + " if data[\"type\"] == \"POINT\":\n", + " print(\" image point: %s\" % data[\"ipt\"])\n", + " print(\" world point: %s\" % data[\"wpt\"])\n", + " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", + " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", + " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", "\n", "\n", - " \n", "fc.add_listener(listener1)" ] }, @@ -111,6 +116,7 @@ "outputs": [], "source": [ "from firefly_client import __version__ as v\n", + "\n", "v" ] }, diff --git a/test/test_socket_not_added_until_listener.ipynb b/test/test_socket_not_added_until_listener.ipynb index 8be6285..561ef4c 100644 --- a/test/test_socket_not_added_until_listener.ipynb +++ b/test/test_socket_not_added_until_listener.ipynb @@ -7,7 +7,7 @@ "outputs": [], "source": [ "from firefly_client import FireflyClient\n", - "#import astropy.utils.data" + "# import astropy.utils.data" ] }, { @@ -17,14 +17,14 @@ "outputs": [], "source": [ "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", - "lsst_demo_host = 'https://lsst-demo.ncsa.illinois.edu/firefly'\n", - "local_host = 'http://127.0.0.1:8080/firefly'\n", + "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", + "local_host = \"http://127.0.0.1:8080/firefly\"\n", "host = local_host\n", - "channel1 = 'channel-test-1'\n", - "channel2 = 'channel-test-2'\n", - "channel3 = 'channel-test-3'\n", + "channel1 = \"channel-test-1\"\n", + "channel2 = \"channel-test-2\"\n", + "channel3 = \"channel-test-3\"\n", "\n", - "fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n" + "fc1_c1 = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)" ] }, { @@ -44,8 +44,9 @@ } ], "source": [ - "\n", - "fc1_c1.show_fits(url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\")" + "fc1_c1.show_fits(\n", + " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\"\n", + ")" ] }, { @@ -67,9 +68,9 @@ "source": [ "# Extensions can be made but there is not web socket connections until a listener is added\n", "\n", - "fc1_c1.add_extension(ext_type='LINE_SELECT', title='a line')\n", - "fc1_c1.add_extension(ext_type='AREA_SELECT', title='a area')\n", - "fc1_c1.add_extension(ext_type='POINT', title='a point')" + "fc1_c1.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\")\n", + "fc1_c1.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", + "fc1_c1.add_extension(ext_type=\"POINT\", title=\"a point\")" ] }, { @@ -89,10 +90,12 @@ "source": [ "# A Web socket should not be made until this cell is added\n", "\n", + "\n", "def listener1(ev):\n", - " print('l1')\n", + " print(\"l1\")\n", " print(ev)\n", - " \n", + "\n", + "\n", "fc1_c1.add_listener(listener1)" ] }, diff --git a/uv.lock b/uv.lock index ee052eb..975f402 100644 --- a/uv.lock +++ b/uv.lock @@ -1,6 +1,10 @@ version = 1 revision = 1 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.11'", + "python_full_version < '3.11'", +] [[package]] name = "accessible-pygments" @@ -23,6 +27,100 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, ] +[[package]] +name = "astropy" +version = "6.1.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +dependencies = [ + { name = "astropy-iers-data", marker = "python_full_version < '3.11'" }, + { name = "numpy", marker = "python_full_version < '3.11'" }, + { name = "packaging", marker = "python_full_version < '3.11'" }, + { name = "pyerfa", marker = "python_full_version < '3.11'" }, + { name = "pyyaml", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/f8/9c6675ab4c646b95aae2762d108f6be4504033d91bd50da21daa62cab5ce/astropy-6.1.7.tar.gz", hash = "sha256:a405ac186306b6cb152e6df2f7444ab8bd764e4127d7519da1b3ae4dd65357ef", size = 7063411 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/4f/27f91eb9cdaa37835e52496dcad00fd89969ef5154795697987d031d0605/astropy-6.1.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be954c5f7707a089609053665aeb76493b79e5c4753c39486761bc6d137bf040", size = 6531137 }, + { url = "https://files.pythonhosted.org/packages/4b/f2/fb2c6c1d31c21df0d4409ecd5e9788795be6f8f80b67008c8191488d55cf/astropy-6.1.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b5e48df5ab2e3e521e82a7233a4b1159d071e64e6cbb76c45415dc68d3b97af1", size = 6410376 }, + { url = "https://files.pythonhosted.org/packages/fd/68/65ad3ea77440df2e8625d8fee585d5fc6049f33a61e49221f91d8de0e3df/astropy-6.1.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55c78252633c644361e2f7092d71f80ef9c2e6649f08d97711d9f19af514aedc", size = 9892774 }, + { url = "https://files.pythonhosted.org/packages/b4/41/e366fc5baff41f7b433f07a46c053a24459e93d2912690d099f0eefabfc3/astropy-6.1.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:985e5e74489d23f1a11953b6b283fccde3f46cb6c68fee4f7228e5f6d8350ba9", size = 9962419 }, + { url = "https://files.pythonhosted.org/packages/1e/a0/e6c1ef80f7e20fb600b3af742d227e6356704dbda3763ff1d76a53a0fd7b/astropy-6.1.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dc2ea28ed41a3d92c39b1481d9c5be016ae58d68f144f3fd8cecffe503525bab", size = 9987760 }, + { url = "https://files.pythonhosted.org/packages/49/93/6b23e75d690763a9d702038c74ea9a74181a278fe362fbeecea35b691e4a/astropy-6.1.7-cp310-cp310-win32.whl", hash = "sha256:4e4badadd8dfa5dca08fd86e9a50a3a91af321975859f5941579e6b7ce9ba199", size = 6274137 }, + { url = "https://files.pythonhosted.org/packages/6e/e1/af92dc2132547e3998476a4b0ab19d15c50d8ec1d85e658fe6503e125fd1/astropy-6.1.7-cp310-cp310-win_amd64.whl", hash = "sha256:8d7f6727689288ee08fc0a4a297fc7e8089d01718321646bd00fea0906ad63dc", size = 6394810 }, + { url = "https://files.pythonhosted.org/packages/4f/5e/d31204823764f6e5fa4820c1b4f49f8eef7cf691b796ec389f41b4f5a699/astropy-6.1.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:09edca01276ee63f7b2ff511da9bfb432068ba3242e27ef27d76e5a171087b7e", size = 6531221 }, + { url = "https://files.pythonhosted.org/packages/22/e2/ae5dd6d9272e41619d85df4e4a03cf06acea8bcb44c42fe67e5cd04ae131/astropy-6.1.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:072f62a67992393beb016dc80bee8fb994fda9aa69e945f536ed8ac0e51291e6", size = 6409477 }, + { url = "https://files.pythonhosted.org/packages/01/ed/9bc17beb457943ee04b8c85614ddb4a64a4a91597340dca28332e112209d/astropy-6.1.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2706156d3646f9c9a7fc810475d8ab0df4c717beefa8326552576a0f8ddca20", size = 10150734 }, + { url = "https://files.pythonhosted.org/packages/39/38/1c5263f0d775def518707ccd1cf9d4df1d99d523fc148df9e38aa5ba9d54/astropy-6.1.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fcd99e627692f8e58bb3097d330bfbd109a22e00dab162a67f203b0a0601ad2c", size = 10210679 }, + { url = "https://files.pythonhosted.org/packages/32/d1/7365e16b0158f755977a5bdbd329df40a9772b0423a1d5075aba9246673f/astropy-6.1.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b0ebbcb637b2e9bcb73011f2b7890d7a3f5a41b66ccaad7c28f065e81e28f0b2", size = 10245960 }, + { url = "https://files.pythonhosted.org/packages/e9/b6/4dc6f9ef1c17738b8ebd8922bc1c6fec48542ccfe5124b6719737b012b8c/astropy-6.1.7-cp311-cp311-win32.whl", hash = "sha256:192b12ede49cd828362ab1a6ede2367fe203f4d851804ec22fa92e009a524281", size = 6272124 }, + { url = "https://files.pythonhosted.org/packages/ba/c6/b5f33597bfbc1afad0640b20000633127dfa0a4295b607a0439f45546d9a/astropy-6.1.7-cp311-cp311-win_amd64.whl", hash = "sha256:3cac64bcdf570c947019bd2bc96711eeb2c7763afe192f18c9551e52a6c296b2", size = 6396627 }, + { url = "https://files.pythonhosted.org/packages/46/2b/007c888fead170c714ecdcf56bc59e8d3252776bd3f16e1797158a46f65d/astropy-6.1.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f2a8bcbb1306052cc38c9eed2c9331bfafe2582b499a7321946abf74b26eb256", size = 6535604 }, + { url = "https://files.pythonhosted.org/packages/8e/4c/cc30c9b1440f4a2f1f52845873ae3f8f7c4343261e516603a35546574ed7/astropy-6.1.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eaf88878684f9d31aff36475c90d101f4cff22fdd4fd50098d9950fd56994df7", size = 6415117 }, + { url = "https://files.pythonhosted.org/packages/12/2d/9985b8b4225c2495c4e64713d1630937c83af863db606d12676b72b4f651/astropy-6.1.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb8cd231e53556e4eebe0393ea95a8cea6b2ff4187c95ac4ff8b17e7a8da823", size = 10177861 }, + { url = "https://files.pythonhosted.org/packages/b7/b6/63ccb085757638d15f0f9d6f2dffaccce7785236fe8bf23e4b380a333ce0/astropy-6.1.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ad36334d138a4f71d6fdcf225a98ad1dad6c343da4362d5a47a71f5c9da3ca9", size = 10258014 }, + { url = "https://files.pythonhosted.org/packages/c8/ee/a6af891802de463f70e3fddf09f3aeb1d46dde87885e2245d25a2ac46948/astropy-6.1.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dd731c526869d0c68507be7b31dd10871b7c44d310bb5495476505560c83cd33", size = 10277363 }, + { url = "https://files.pythonhosted.org/packages/dd/98/b253583f9de7033f03a7c5f5314b9e93177725a2020e0f36d338d242bf0e/astropy-6.1.7-cp312-cp312-win32.whl", hash = "sha256:662bacd7ae42561e038cbd85eea3b749308cf3575611a745b60f034d3350c97a", size = 6271741 }, + { url = "https://files.pythonhosted.org/packages/7a/63/e1b5f01e6735ed8f9d62d3eed5f226bc0ab516ab8558ffaccf6d4185f91d/astropy-6.1.7-cp312-cp312-win_amd64.whl", hash = "sha256:5b4d02a98a0bf91ff7fd4ef0bd0ecca83c9497338cb88b61ec9f971350688222", size = 6396352 }, + { url = "https://files.pythonhosted.org/packages/73/9d/21d2e61080a81e7e1f5e5006204a76e70588aa1a88aa9044c2d203578d07/astropy-6.1.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fbeaf04427987c0c6fa2e579eb40011802b06fba6b3a7870e082d5c693564e1b", size = 6528360 }, + { url = "https://files.pythonhosted.org/packages/d5/3e/b999ec6cd607c512e66d8a138443361eb88899760c7cb8517a66155732ee/astropy-6.1.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab6e88241a14185b9404b02246329185b70292984aa0616b20a0628dfe4f4ebb", size = 6407905 }, + { url = "https://files.pythonhosted.org/packages/db/2d/44557c63688c2ed03d0d72b4f27fc30fc1ea250aeb5ebd939796c5f98bee/astropy-6.1.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0529c75565feaabb629946806b4763ae7b02069aeff4c3b56a69e8a9e638500", size = 10106849 }, + { url = "https://files.pythonhosted.org/packages/66/bc/993552eb932dec528fe6b95f511e918473ea4406dee4b17c223f3fd8a919/astropy-6.1.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c5ec347631da77573fc729ba04e5d89a3bc94500bf6037152a2d0f9965ae1ce", size = 10194766 }, + { url = "https://files.pythonhosted.org/packages/9f/f3/3c5282762c8a5746e7752e46a1e328c79a5d0186d96cfd0995bdf976e1f9/astropy-6.1.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc496f87aaccaa5c6624acc985b8770f039c5bbe74b120c8ed7bad3698e24e1b", size = 10219291 }, + { url = "https://files.pythonhosted.org/packages/d8/52/949bb79df9c03f56d0ae93ac62f2616fe3e67db51677bf412473bf6d077e/astropy-6.1.7-cp313-cp313-win32.whl", hash = "sha256:b1e01d534383c038dbf8664b964fa4ea818c7419318830d3c732c750c64115c6", size = 6269501 }, + { url = "https://files.pythonhosted.org/packages/a1/da/f369561a67061dd42e13c7f758b393ae90319dbbcf7e301a18ce3fa43ec6/astropy-6.1.7-cp313-cp313-win_amd64.whl", hash = "sha256:af08cf2b0368f1ea585eb26a55d99a2de9e9b0bd30aba84b5329059c3ec33590", size = 6393207 }, +] + +[[package]] +name = "astropy" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.11'", +] +dependencies = [ + { name = "astropy-iers-data", marker = "python_full_version >= '3.11'" }, + { name = "numpy", marker = "python_full_version >= '3.11'" }, + { name = "packaging", marker = "python_full_version >= '3.11'" }, + { name = "pyerfa", marker = "python_full_version >= '3.11'" }, + { name = "pyyaml", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/12/a1c582b3f9df5e2680eae0ed82c8057ae06d6130a1f7a5c0770fa899737e/astropy-7.0.1.tar.gz", hash = "sha256:392feeb443b2437cd4c2e0641a65e0f15ba791e148e9b1e5ed7de7dfcb38e460", size = 7139539 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/79/ab4edcdb5928ed636b24d6e721460880d0bb6a7a2bca228d06abb5d2a5e4/astropy-7.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b16c5e3cd30ecedc79401a931eaeae3b9f2fc17f19ea26693c6c424cda74bd0e", size = 6574981 }, + { url = "https://files.pythonhosted.org/packages/57/34/87a0feca0214f21092c75381b046b9eb71cf56ea406f6148aa34cf30c3bb/astropy-7.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11c680207674a53afd603addf5640809bb670f72e9f9a6f023b862040ba7e100", size = 6499230 }, + { url = "https://files.pythonhosted.org/packages/4e/08/e112c26a69ce00238d3d03b5d200d86816b9db5b582169957a9102416aae/astropy-7.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e930aa618a1df80ecc38ecc594ca1e7508909c79c4b1145e9e91e8695fd163c", size = 10251470 }, + { url = "https://files.pythonhosted.org/packages/75/9d/1e150830a5491b684e2bb8c8e7224702ac57c101b4e208ed8f84eeedbf5a/astropy-7.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b4831c5b8a8e6c62afc095d9f65e6c023f058821c1fcc829adc2d941090cd2a", size = 10310513 }, + { url = "https://files.pythonhosted.org/packages/5c/f0/2c4cd9fbbdb222aaafe902fe39e62851ab8fe25b394f328d68df0d0f2301/astropy-7.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2245f23020db2382f187a880dd903f1d4747cbdce6af8f299bc6d4f256b382bf", size = 10345694 }, + { url = "https://files.pythonhosted.org/packages/5a/0c/f0c7624e0db2b81ff7e2bb8fbf3c0403c9f4e9ed29a99132af51d26b54b6/astropy-7.0.1-cp311-cp311-win32.whl", hash = "sha256:91bec2ac62f759300279fd98f3baf2fe33082729045d80c231948d6df0cceacc", size = 6364686 }, + { url = "https://files.pythonhosted.org/packages/65/61/b85c917adbd0a69ebe0e512285abea1ae81e1a713ae55452b4f20afdb1e7/astropy-7.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:0ae8960fe690f62672afff723d5e7d25ccd054c941c3ded96471d4d62563864e", size = 6489635 }, + { url = "https://files.pythonhosted.org/packages/74/e2/3dc27026a098171cf3f88d964dfdf39c70c75fd205624f2f9be8381268f7/astropy-7.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b075f4b8034a3e40d417c079232f1adb613a40e3b8d7cebdb05278666bfeb3ca", size = 6581263 }, + { url = "https://files.pythonhosted.org/packages/2d/45/0ae6513fc819a9223c5a779b2955ceb27ab5ad57c44e16abcc9fbaee838c/astropy-7.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71bb61871859635653e0530ca152c379882473ff4aabf2484a848ead94efe248", size = 6504874 }, + { url = "https://files.pythonhosted.org/packages/ae/f8/2d151d9476eb8797923164aee9764873c28be3654c7c60eae5d33f916d30/astropy-7.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1692857a3f21cecadefe89e4371e9c5a2d03e80173f4bb01dcbe3e3213ad1303", size = 10279377 }, + { url = "https://files.pythonhosted.org/packages/ed/70/930a24bd3f2046ba2c4b8a914d0349ffb17760d044f2a4e8c3195b984851/astropy-7.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062f9dc6549884699c551310a21434ff650c9a1cb6c0eea9476ead456f8a8500", size = 10358814 }, + { url = "https://files.pythonhosted.org/packages/22/07/28bc41436ada5e614a583371780d03f6377aa845450d0b04d544dee5c49c/astropy-7.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9cd02ef44007e0d6f27f0136410253a64b1ae56a2333c7071fd775c6feea8e12", size = 10377323 }, + { url = "https://files.pythonhosted.org/packages/4e/56/175acf7145af6c77cfeca339837293b2dee06936280bf841b6df8da30c92/astropy-7.0.1-cp312-cp312-win32.whl", hash = "sha256:ba9c09cb1ba9c126b40cbbbe9273b4e6f2b0403d268f64a15ed976f77754372b", size = 6364259 }, + { url = "https://files.pythonhosted.org/packages/69/a8/ae5bb7849274b04248817fc8c885c0966c02c7d20a9a7f9e7388fa9f091f/astropy-7.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:02259457097a3bad3e50975d67bd7586753b2a69d680f071be5c8da439e0f2c4", size = 6489380 }, + { url = "https://files.pythonhosted.org/packages/e2/f0/b4a484970e6227ea94cacea93d7dbd38bbef9c368e60ef3a6e00fe21d7b8/astropy-7.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:012e4331535dae6f43ddf86295b05e711e1ff6dec7af9d77a887ef08e6cef60d", size = 6574160 }, + { url = "https://files.pythonhosted.org/packages/10/87/cb34793d62e33f97f61f2cec9e4651e8f14786e1810a78fae6e7209b6232/astropy-7.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0785533096b5682148ab79b077ab0e17f254be17c9fa12cb7789b8109044fe95", size = 6497653 }, + { url = "https://files.pythonhosted.org/packages/b0/0c/4e8552bcfc4d12f9e2953f39336c554527261361277968aeada918208285/astropy-7.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5949792533238a601597574ac85599f0fab2db2480e0121c58121d808626698f", size = 10208169 }, + { url = "https://files.pythonhosted.org/packages/9b/a3/33546e62bb936d8dcceb24e7ef7540005c1dfbbe341715b217d2867e7cc2/astropy-7.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a367d334b789934b975565be470eb1043b3419508bd8e15a882775925e30d2ce", size = 10295439 }, + { url = "https://files.pythonhosted.org/packages/1b/a8/17246827967f6b4b6f5fa69ae1bb4a6e26370e3eb0d64185da2c3fa3911a/astropy-7.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:43c6dac6454e1056649268e79c3f595ecf079ad762936c6ce5c738ee1cff53de", size = 10319045 }, + { url = "https://files.pythonhosted.org/packages/48/6c/31730227b985744c7a58f7ebad1768f9501b4f1f4923ccd58e6f13fa6308/astropy-7.0.1-cp313-cp313-win32.whl", hash = "sha256:b458e4ac2466cb8bfeb0a9ed1fa7e96a9955d03902c1e5ce4ec8881bdfefaf4a", size = 6362116 }, + { url = "https://files.pythonhosted.org/packages/53/ef/eeeb9eb42cf5a83abf704043fafe04417849997321a20fcd465f32ed4b19/astropy-7.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:8ffb20105eeec84f6bd8bbde4a86686a8f78424c3c46b6021fbf816e40dd8960", size = 6486463 }, +] + +[[package]] +name = "astropy-iers-data" +version = "0.2025.2.17.0.34.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/8f/5b84d3b0cb98708d5bb8b4f1442ca9bdfd3dc1ffd4a1cde715e714e3ff85/astropy_iers_data-0.2025.2.17.0.34.13.tar.gz", hash = "sha256:34b685447a32e63f43811adcee2588bcd55dd77dfc454a05c25f0bbf80066960", size = 1893113 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/e2/496d0136d7b1446ad2cfcd4aacae8220301d9789400a916da8d79019c487/astropy_iers_data-0.2025.2.17.0.34.13-py3-none-any.whl", hash = "sha256:2024e3251e1360cdbd978ccf19152610396d2324f5b2af19746e903e243b7566", size = 1945936 }, +] + [[package]] name = "babel" version = "2.17.0" @@ -204,6 +302,8 @@ name = "firefly-client" version = "3.2.0" source = { editable = "." } dependencies = [ + { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "astropy", version = "7.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "requests" }, { name = "websocket-client" }, ] @@ -224,6 +324,7 @@ test = [ [package.metadata] requires-dist = [ + { name = "astropy", specifier = ">=6.1.7" }, { name = "myst-parser", marker = "extra == 'docs'" }, { name = "pydata-sphinx-theme", marker = "extra == 'docs'" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.4" }, @@ -384,6 +485,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579 }, ] +[[package]] +name = "numpy" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/90/8956572f5c4ae52201fdec7ba2044b2c882832dcec7d5d0922c9e9acf2de/numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", size = 20262700 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/e1/1816d5d527fa870b260a1c2c5904d060caad7515637bd54f495a5ce13ccd/numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71", size = 21232911 }, + { url = "https://files.pythonhosted.org/packages/29/46/9f25dc19b359f10c0e52b6bac25d3181eb1f4b4d04c9846a32cf5ea52762/numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787", size = 14371955 }, + { url = "https://files.pythonhosted.org/packages/72/d7/de941296e6b09a5c81d3664ad912f1496a0ecdd2f403318e5e35604ff70f/numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716", size = 5410476 }, + { url = "https://files.pythonhosted.org/packages/36/ce/55f685995110f8a268fdca0f198c9a84fa87b39512830965cc1087af6391/numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b", size = 6945730 }, + { url = "https://files.pythonhosted.org/packages/4f/84/abdb9f6e22576d89c259401c3234d4755b322539491bbcffadc8bcb120d3/numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3", size = 14350752 }, + { url = "https://files.pythonhosted.org/packages/e9/88/3870cfa9bef4dffb3a326507f430e6007eeac258ebeef6b76fc542aef66d/numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52", size = 16399386 }, + { url = "https://files.pythonhosted.org/packages/02/10/3f629682dd0b457525c131945329c4e81e2dadeb11256e6ce4c9a1a6fb41/numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b", size = 15561826 }, + { url = "https://files.pythonhosted.org/packages/da/18/fd35673ba9751eba449d4ce5d24d94e3b612cdbfba79348da71488c0b7ac/numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027", size = 18188593 }, + { url = "https://files.pythonhosted.org/packages/ce/4c/c0f897b580ea59484b4cc96a441fea50333b26675a60a1421bc912268b5f/numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094", size = 6590421 }, + { url = "https://files.pythonhosted.org/packages/e5/5b/aaabbfc7060c5c8f0124c5deb5e114a3b413a548bbc64e372c5b5db36165/numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb", size = 12925667 }, + { url = "https://files.pythonhosted.org/packages/96/86/453aa3949eab6ff54e2405f9cb0c01f756f031c3dc2a6d60a1d40cba5488/numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", size = 21237256 }, + { url = "https://files.pythonhosted.org/packages/20/c3/93ecceadf3e155d6a9e4464dd2392d8d80cf436084c714dc8535121c83e8/numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", size = 14408049 }, + { url = "https://files.pythonhosted.org/packages/8d/29/076999b69bd9264b8df5e56f2be18da2de6b2a2d0e10737e5307592e01de/numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", size = 5408655 }, + { url = "https://files.pythonhosted.org/packages/e2/a7/b14f0a73eb0fe77cb9bd5b44534c183b23d4229c099e339c522724b02678/numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", size = 6949996 }, + { url = "https://files.pythonhosted.org/packages/72/2f/8063da0616bb0f414b66dccead503bd96e33e43685c820e78a61a214c098/numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", size = 14355789 }, + { url = "https://files.pythonhosted.org/packages/e6/d7/3cd47b00b8ea95ab358c376cf5602ad21871410950bc754cf3284771f8b6/numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", size = 16411356 }, + { url = "https://files.pythonhosted.org/packages/27/c0/a2379e202acbb70b85b41483a422c1e697ff7eee74db642ca478de4ba89f/numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", size = 15576770 }, + { url = "https://files.pythonhosted.org/packages/bc/63/a13ee650f27b7999e5b9e1964ae942af50bb25606d088df4229283eda779/numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", size = 18200483 }, + { url = "https://files.pythonhosted.org/packages/4c/87/e71f89935e09e8161ac9c590c82f66d2321eb163893a94af749dfa8a3cf8/numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", size = 6588415 }, + { url = "https://files.pythonhosted.org/packages/b9/c6/cd4298729826af9979c5f9ab02fcaa344b82621e7c49322cd2d210483d3f/numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", size = 12929604 }, + { url = "https://files.pythonhosted.org/packages/43/ec/43628dcf98466e087812142eec6d1c1a6c6bdfdad30a0aa07b872dc01f6f/numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", size = 20929458 }, + { url = "https://files.pythonhosted.org/packages/9b/c0/2f4225073e99a5c12350954949ed19b5d4a738f541d33e6f7439e33e98e4/numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", size = 14115299 }, + { url = "https://files.pythonhosted.org/packages/ca/fa/d2c5575d9c734a7376cc1592fae50257ec95d061b27ee3dbdb0b3b551eb2/numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", size = 5145723 }, + { url = "https://files.pythonhosted.org/packages/eb/dc/023dad5b268a7895e58e791f28dc1c60eb7b6c06fcbc2af8538ad069d5f3/numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", size = 6678797 }, + { url = "https://files.pythonhosted.org/packages/3f/19/bcd641ccf19ac25abb6fb1dcd7744840c11f9d62519d7057b6ab2096eb60/numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", size = 14067362 }, + { url = "https://files.pythonhosted.org/packages/39/04/78d2e7402fb479d893953fb78fa7045f7deb635ec095b6b4f0260223091a/numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", size = 16116679 }, + { url = "https://files.pythonhosted.org/packages/d0/a1/e90f7aa66512be3150cb9d27f3d9995db330ad1b2046474a13b7040dfd92/numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", size = 15264272 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/50bd027cca494de4fa1fc7bf1662983d0ba5f256fa0ece2c376b5eb9b3f0/numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", size = 17880549 }, + { url = "https://files.pythonhosted.org/packages/96/30/f7bf4acb5f8db10a96f73896bdeed7a63373137b131ca18bd3dab889db3b/numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", size = 6293394 }, + { url = "https://files.pythonhosted.org/packages/42/6e/55580a538116d16ae7c9aa17d4edd56e83f42126cb1dfe7a684da7925d2c/numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", size = 12626357 }, + { url = "https://files.pythonhosted.org/packages/0e/8b/88b98ed534d6a03ba8cddb316950fe80842885709b58501233c29dfa24a9/numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", size = 20916001 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/def6ec32c725cc5fbd8bdf8af80f616acf075fe752d8a23e895da8c67b70/numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", size = 14130721 }, + { url = "https://files.pythonhosted.org/packages/20/60/70af0acc86495b25b672d403e12cb25448d79a2b9658f4fc45e845c397a8/numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", size = 5130999 }, + { url = "https://files.pythonhosted.org/packages/2e/69/d96c006fb73c9a47bcb3611417cf178049aae159afae47c48bd66df9c536/numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", size = 6665299 }, + { url = "https://files.pythonhosted.org/packages/5a/3f/d8a877b6e48103733ac224ffa26b30887dc9944ff95dffdfa6c4ce3d7df3/numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", size = 14064096 }, + { url = "https://files.pythonhosted.org/packages/e4/43/619c2c7a0665aafc80efca465ddb1f260287266bdbdce517396f2f145d49/numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", size = 16114758 }, + { url = "https://files.pythonhosted.org/packages/d9/79/ee4fe4f60967ccd3897aa71ae14cdee9e3c097e3256975cc9575d393cb42/numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", size = 15259880 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/8b55cf05db6d85b7a7d414b3d1bd5a740706df00bfa0824a08bf041e52ee/numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", size = 17876721 }, + { url = "https://files.pythonhosted.org/packages/21/d6/b4c2f0564b7dcc413117b0ffbb818d837e4b29996b9234e38b2025ed24e7/numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", size = 6290195 }, + { url = "https://files.pythonhosted.org/packages/97/e7/7d55a86719d0de7a6a597949f3febefb1009435b79ba510ff32f05a8c1d7/numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", size = 12619013 }, + { url = "https://files.pythonhosted.org/packages/a6/1f/0b863d5528b9048fd486a56e0b97c18bf705e88736c8cea7239012119a54/numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", size = 20944621 }, + { url = "https://files.pythonhosted.org/packages/aa/99/b478c384f7a0a2e0736177aafc97dc9152fc036a3fdb13f5a3ab225f1494/numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", size = 14142502 }, + { url = "https://files.pythonhosted.org/packages/fb/61/2d9a694a0f9cd0a839501d362de2a18de75e3004576a3008e56bdd60fcdb/numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", size = 5176293 }, + { url = "https://files.pythonhosted.org/packages/33/35/51e94011b23e753fa33f891f601e5c1c9a3d515448659b06df9d40c0aa6e/numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", size = 6691874 }, + { url = "https://files.pythonhosted.org/packages/ff/cf/06e37619aad98a9d03bd8d65b8e3041c3a639be0f5f6b0a0e2da544538d4/numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", size = 14036826 }, + { url = "https://files.pythonhosted.org/packages/0c/93/5d7d19955abd4d6099ef4a8ee006f9ce258166c38af259f9e5558a172e3e/numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", size = 16096567 }, + { url = "https://files.pythonhosted.org/packages/af/53/d1c599acf7732d81f46a93621dab6aa8daad914b502a7a115b3f17288ab2/numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", size = 15242514 }, + { url = "https://files.pythonhosted.org/packages/53/43/c0f5411c7b3ea90adf341d05ace762dad8cb9819ef26093e27b15dd121ac/numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", size = 17872920 }, + { url = "https://files.pythonhosted.org/packages/5b/57/6dbdd45ab277aff62021cafa1e15f9644a52f5b5fc840bc7591b4079fb58/numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", size = 6346584 }, + { url = "https://files.pythonhosted.org/packages/97/9b/484f7d04b537d0a1202a5ba81c6f53f1846ae6c63c2127f8df869ed31342/numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", size = 12706784 }, + { url = "https://files.pythonhosted.org/packages/0a/b5/a7839f5478be8f859cb880f13d90fcfe4b0ec7a9ebaff2bcc30d96760596/numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d", size = 21064244 }, + { url = "https://files.pythonhosted.org/packages/29/e8/5da32ffcaa7a72f7ecd82f90c062140a061eb823cb88e90279424e515cf4/numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9", size = 6809418 }, + { url = "https://files.pythonhosted.org/packages/a8/a9/68aa7076c7656a7308a0f73d0a2ced8c03f282c9fd98fa7ce21c12634087/numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e", size = 16215461 }, + { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, +] + [[package]] name = "packaging" version = "24.2" @@ -429,6 +592,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264 }, ] +[[package]] +name = "pyerfa" +version = "2.0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/39/63cc8291b0cf324ae710df41527faf7d331bce573899199d926b3e492260/pyerfa-2.0.1.5.tar.gz", hash = "sha256:17d6b24fe4846c65d5e7d8c362dcb08199dc63b30a236aedd73875cc83e1f6c0", size = 818430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/d9/3448a57cb5bd19950de6d6ab08bd8fbb3df60baa71726de91d73d76c481b/pyerfa-2.0.1.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b282d7c60c4c47cf629c484c17ac504fcb04abd7b3f4dfcf53ee042afc3a5944", size = 341818 }, + { url = "https://files.pythonhosted.org/packages/11/4a/31a363370478b63c6289a34743f2ba2d3ae1bd8223e004d18ab28fb92385/pyerfa-2.0.1.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:be1aeb70390dd03a34faf96749d5cabc58437410b4aab7213c512323932427df", size = 329370 }, + { url = "https://files.pythonhosted.org/packages/cb/96/b6210fc624123c8ae13e1eecb68fb75e3f3adff216d95eee1c7b05843e3e/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0603e8e1b839327d586c8a627cdc634b795e18b007d84f0cda5500a0908254e", size = 692794 }, + { url = "https://files.pythonhosted.org/packages/e5/e0/050018d855d26d3c0b4a7d1b2ed692be758ce276d8289e2a2b44ba1014a5/pyerfa-2.0.1.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e43c7194e3242083f2350b46c09fd4bf8ba1bcc0ebd1460b98fc47fe2389906", size = 738711 }, + { url = "https://files.pythonhosted.org/packages/b9/f5/ff91ee77308793ae32fa1e1de95e9edd4551456dd888b4e87c5938657ca5/pyerfa-2.0.1.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:07b80cd70701f5d066b1ac8cce406682cfcd667a1186ec7d7ade597239a6021d", size = 722966 }, + { url = "https://files.pythonhosted.org/packages/2c/56/b22b35c8551d2228ff8d445e63787112927ca13f6dc9e2c04f69d742c95b/pyerfa-2.0.1.5-cp39-abi3-win32.whl", hash = "sha256:d30b9b0df588ed5467e529d851ea324a67239096dd44703125072fd11b351ea2", size = 339955 }, + { url = "https://files.pythonhosted.org/packages/b4/11/97233cf23ad5411ac6f13b1d6ee3888f90ace4f974d9bf9db887aa428912/pyerfa-2.0.1.5-cp39-abi3-win_amd64.whl", hash = "sha256:66292d437dcf75925b694977aa06eb697126e7b86553e620371ed3e48b5e0ad0", size = 349410 }, +] + [[package]] name = "pygments" version = "2.19.1" From cfb718a21822ce71f87c149a7b60edfaa5751739 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Tue, 18 Feb 2025 17:45:57 -0800 Subject: [PATCH 03/10] Working tests (except for the hardcoded path) --- port_check.lock | 0 pyproject.toml | 28 +- test/test_5_instances_3_channels.py | 24 +- test/test_basic_tableload.py | 14 +- test/test_simple_callback.py | 16 +- test/test_simple_callback_response.py | 17 +- test/test_socket_not_added_until_listener.py | 14 +- uv.lock | 1178 ++++++++++++++++++ 8 files changed, 1256 insertions(+), 35 deletions(-) create mode 100644 port_check.lock diff --git a/port_check.lock b/port_check.lock new file mode 100644 index 0000000..e69de29 diff --git a/pyproject.toml b/pyproject.toml index fca4207..474732d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,11 +10,7 @@ authors = [{ name = "IPAC LSST SUIT" }] readme = "README.md" license = { file = "License.txt" } requires-python = ">=3.10" -dependencies = [ - "websocket-client", - "requests", - "astropy>=6.1.7", -] +dependencies = ["websocket-client", "requests", "astropy>=6.1.7"] keywords = [ "jupyter", "firefly", @@ -57,10 +53,10 @@ test = [ ] [tool.pytest.ini_options] -testpaths = ["firefly_client", "docs"] +testpaths = ["firefly_client", "docs", "test"] doctest_plus = "enabled" text_file_format = "rst" -addopts = "--doctest-rst" +addopts = "--doctest-rst -n 4" [tool.coverage.run] omit = [ @@ -227,6 +223,24 @@ docstring-code-format = true # enabled. docstring-code-line-length = "dynamic" +[dependency-groups] +docs = [ + "myst-parser>=4.0.1", + "pydata-sphinx-theme>=0.16.1", + "sphinx~=7.1.0", + "sphinx-automodapi>=0.18.0", +] +test = [ + "ipykernel>=6.29.5", + "jupyter-firefly-extensions>=4.3.1", + "pytest>=8.3.4", + "pytest-container>=0.4.3", + "pytest-cov>=6.0.0", + "pytest-doctestplus>=1.4.0", + "pytest-xdist>=3.6.1", + "tox>=4.24.1", +] + [tool.ruff.lint.isort] case-sensitive = true combine-as-imports = true diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py index 0b0043f..7825723 100644 --- a/test/test_5_instances_3_channels.py +++ b/test/test_5_instances_3_channels.py @@ -1,17 +1,18 @@ +import time +import random from firefly_client import FireflyClient from pytest_container.container import Container, ContainerData, EntrypointSelection from pytest_container.inspect import NetworkProtocol, PortForwarding FIREFLY_CONTAINER = Container( - url="docker.io/irsa/firefly:latest", + url="docker.io/ipac/firefly:latest", extra_launch_args=["--memory=4g"], - singleton=True, entry_point=EntrypointSelection.AUTO, forwarded_ports=[ PortForwarding( container_port=8080, protocol=NetworkProtocol.TCP, - host_port=8080, + host_port=random.randint(8000,65534), bind_ip="127.0.0.1", ) ], @@ -21,7 +22,12 @@ def test_5_instances_3_channels(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port == 8080 + assert auto_container.forwarded_ports[0].host_port >= 8000 + assert auto_container.forwarded_ports[0].host_port <= 65534 + assert auto_container.forwarded_ports[0].container_port == 8080 + assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) def listener1(ev): print("l1") @@ -59,7 +65,7 @@ def listener4(ev): ) fc1_c1 = FireflyClient.make_client( - host, channel_override=channel1, launch_browser=True + host, channel_override=channel1, launch_browser=False ) fc1_c1.add_extension(ext_type="POINT", title="a point") # fc.add_extension(ext_type='LINE_SELECT', title='a line') @@ -70,11 +76,11 @@ def listener4(ev): host, channel_override=channel1, launch_browser=False ) fc1_c2 = FireflyClient.make_client( - host, channel_override=channel2, launch_browser=True + host, channel_override=channel2, launch_browser=False ) fc1_c2.add_extension(ext_type="POINT", title="a point") fc1_c3 = FireflyClient.make_client( - host, channel_override=channel3, launch_browser=True + host, channel_override=channel3, launch_browser=False ) print(f">>>>>> channel: {channel1}, firefly url: {fc1_c1.get_firefly_url()}") print(f">>>>>> channel: {channel2}, firefly url: {fc1_c2.get_firefly_url()}") @@ -92,7 +98,3 @@ def listener4(ev): fc2_c1.add_listener(listener2) fc3_c1.add_listener(listener3) fc1_c2.add_listener(listener4) # one web socket should be made for channel2 - - # note - no listener added for channel3, so no websocket made - - fc1_c1.wait_for_events() diff --git a/test/test_basic_tableload.py b/test/test_basic_tableload.py index 09476ef..bea6b07 100644 --- a/test/test_basic_tableload.py +++ b/test/test_basic_tableload.py @@ -1,19 +1,20 @@ import os +import random +import time from firefly_client import FireflyClient from pytest_container.container import Container, ContainerData, EntrypointSelection from pytest_container.inspect import NetworkProtocol, PortForwarding FIREFLY_CONTAINER = Container( - url="docker.io/irsa/firefly:latest", + url="docker.io/ipac/firefly:latest", extra_launch_args=["--memory=4g"], - singleton=True, entry_point=EntrypointSelection.AUTO, forwarded_ports=[ PortForwarding( container_port=8080, protocol=NetworkProtocol.TCP, - host_port=8080, + host_port=random.randint(8000, 65534), bind_ip="127.0.0.1", ) ], @@ -23,7 +24,12 @@ def test_tableload(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port == 8080 + assert auto_container.forwarded_ports[0].host_port >= 8000 + assert auto_container.forwarded_ports[0].host_port <= 65534 + assert auto_container.forwarded_ports[0].container_port == 8080 + assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" fc = FireflyClient.make_client(host) diff --git a/test/test_simple_callback.py b/test/test_simple_callback.py index ce891d6..913ae60 100644 --- a/test/test_simple_callback.py +++ b/test/test_simple_callback.py @@ -1,17 +1,19 @@ +import random +import time + from firefly_client import FireflyClient from pytest_container.container import Container, ContainerData, EntrypointSelection from pytest_container.inspect import NetworkProtocol, PortForwarding FIREFLY_CONTAINER = Container( - url="docker.io/irsa/firefly:latest", + url="docker.io/ipac/firefly:latest", extra_launch_args=["--memory=4g"], - singleton=True, entry_point=EntrypointSelection.AUTO, forwarded_ports=[ PortForwarding( container_port=8080, protocol=NetworkProtocol.TCP, - host_port=8080, + host_port=random.randint(8000, 65534), bind_ip="127.0.0.1", ) ], @@ -21,7 +23,13 @@ def test_simple_callback(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port == 8080 + assert auto_container.forwarded_ports[0].host_port >= 8000 + assert auto_container.forwarded_ports[0].host_port <= 65534 + assert auto_container.forwarded_ports[0].container_port == 8080 + assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" channel1 = "channel-test-1" FireflyClient._debug = False diff --git a/test/test_simple_callback_response.py b/test/test_simple_callback_response.py index a519222..f7910ce 100644 --- a/test/test_simple_callback_response.py +++ b/test/test_simple_callback_response.py @@ -1,18 +1,20 @@ +import random +import time + from firefly_client import FireflyClient from pytest_container.container import Container, ContainerData, EntrypointSelection from pytest_container.inspect import NetworkProtocol, PortForwarding FIREFLY_CONTAINER = Container( - url="docker.io/irsa/firefly:latest", + url="docker.io/ipac/firefly:latest", extra_launch_args=["--memory=4g"], - singleton=True, entry_point=EntrypointSelection.AUTO, forwarded_ports=[ PortForwarding( container_port=8080, protocol=NetworkProtocol.TCP, - host_port=8080, + host_port=random.randint(8000, 65534), bind_ip="127.0.0.1", ) ], @@ -22,7 +24,13 @@ def test_simple_callback_response(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port == 8080 + assert auto_container.forwarded_ports[0].host_port >= 8000 + assert auto_container.forwarded_ports[0].host_port <= 65534 + assert auto_container.forwarded_ports[0].container_port == 8080 + assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + FireflyClient._debug = False token = None host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" @@ -69,4 +77,3 @@ def example_listener(ev): # fc.remove_listener(example_listener) # time.sleep(2) # fc.add_listener(example_listener) - fc.wait_for_events() diff --git a/test/test_socket_not_added_until_listener.py b/test/test_socket_not_added_until_listener.py index ae7ec3b..475bacf 100644 --- a/test/test_socket_not_added_until_listener.py +++ b/test/test_socket_not_added_until_listener.py @@ -1,3 +1,4 @@ +import random import time from firefly_client import FireflyClient @@ -6,15 +7,14 @@ FIREFLY_CONTAINER = Container( - url="docker.io/irsa/firefly:latest", + url="docker.io/ipac/firefly:latest", extra_launch_args=["--memory=4g"], - singleton=True, entry_point=EntrypointSelection.AUTO, forwarded_ports=[ PortForwarding( container_port=8080, protocol=NetworkProtocol.TCP, - host_port=8080, + host_port=random.randint(8000, 65534), bind_ip="127.0.0.1", ) ], @@ -24,7 +24,13 @@ def test_socket_not_added_until_listener(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port == 8080 + assert auto_container.forwarded_ports[0].host_port >= 8000 + assert auto_container.forwarded_ports[0].host_port <= 65534 + assert auto_container.forwarded_ports[0].container_port == 8080 + assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" channel1 = "channel-test-1" diff --git a/uv.lock b/uv.lock index 975f402..f32b6ab 100644 --- a/uv.lock +++ b/uv.lock @@ -27,6 +27,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, ] +[[package]] +name = "anyio" +version = "4.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "argon2-cffi" +version = "23.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, + { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, + { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, + { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, + { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, + { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, + { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, + { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, + { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, + { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, +] + +[[package]] +name = "arrow" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "types-python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, +] + [[package]] name = "astropy" version = "6.1.7" @@ -121,6 +191,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/e2/496d0136d7b1446ad2cfcd4aacae8220301d9789400a916da8d79019c487/astropy_iers_data-0.2025.2.17.0.34.13-py3-none-any.whl", hash = "sha256:2024e3251e1360cdbd978ccf19152610396d2324f5b2af19746e903e243b7566", size = 1945936 }, ] +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, +] + +[[package]] +name = "attrs" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, +] + [[package]] name = "babel" version = "2.17.0" @@ -143,6 +231,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, ] +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + [[package]] name = "cachetools" version = "5.5.1" @@ -161,6 +266,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, ] +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + [[package]] name = "chardet" version = "5.2.0" @@ -240,6 +402,126 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + +[[package]] +name = "coverage" +version = "7.6.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/d6/2b53ab3ee99f2262e6f0b8369a43f6d66658eab45510331c0b3d5c8c4272/coverage-7.6.12.tar.gz", hash = "sha256:48cfc4641d95d34766ad41d9573cc0f22a48aa88d22657a1fe01dca0dbae4de2", size = 805941 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/67/81dc41ec8f548c365d04a29f1afd492d3176b372c33e47fa2a45a01dc13a/coverage-7.6.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:704c8c8c6ce6569286ae9622e534b4f5b9759b6f2cd643f1c1a61f666d534fe8", size = 208345 }, + { url = "https://files.pythonhosted.org/packages/33/43/17f71676016c8829bde69e24c852fef6bd9ed39f774a245d9ec98f689fa0/coverage-7.6.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad7525bf0241e5502168ae9c643a2f6c219fa0a283001cee4cf23a9b7da75879", size = 208775 }, + { url = "https://files.pythonhosted.org/packages/86/25/c6ff0775f8960e8c0840845b723eed978d22a3cd9babd2b996e4a7c502c6/coverage-7.6.12-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06097c7abfa611c91edb9e6920264e5be1d6ceb374efb4986f38b09eed4cb2fe", size = 237925 }, + { url = "https://files.pythonhosted.org/packages/b0/3d/5f5bd37046243cb9d15fff2c69e498c2f4fe4f9b42a96018d4579ed3506f/coverage-7.6.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220fa6c0ad7d9caef57f2c8771918324563ef0d8272c94974717c3909664e674", size = 235835 }, + { url = "https://files.pythonhosted.org/packages/b5/f1/9e6b75531fe33490b910d251b0bf709142e73a40e4e38a3899e6986fe088/coverage-7.6.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3688b99604a24492bcfe1c106278c45586eb819bf66a654d8a9a1433022fb2eb", size = 236966 }, + { url = "https://files.pythonhosted.org/packages/4f/bc/aef5a98f9133851bd1aacf130e754063719345d2fb776a117d5a8d516971/coverage-7.6.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d1a987778b9c71da2fc8948e6f2656da6ef68f59298b7e9786849634c35d2c3c", size = 236080 }, + { url = "https://files.pythonhosted.org/packages/eb/d0/56b4ab77f9b12aea4d4c11dc11cdcaa7c29130b837eb610639cf3400c9c3/coverage-7.6.12-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:cec6b9ce3bd2b7853d4a4563801292bfee40b030c05a3d29555fd2a8ee9bd68c", size = 234393 }, + { url = "https://files.pythonhosted.org/packages/0d/77/28ef95c5d23fe3dd191a0b7d89c82fea2c2d904aef9315daf7c890e96557/coverage-7.6.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ace9048de91293e467b44bce0f0381345078389814ff6e18dbac8fdbf896360e", size = 235536 }, + { url = "https://files.pythonhosted.org/packages/29/62/18791d3632ee3ff3f95bc8599115707d05229c72db9539f208bb878a3d88/coverage-7.6.12-cp310-cp310-win32.whl", hash = "sha256:ea31689f05043d520113e0552f039603c4dd71fa4c287b64cb3606140c66f425", size = 211063 }, + { url = "https://files.pythonhosted.org/packages/fc/57/b3878006cedfd573c963e5c751b8587154eb10a61cc0f47a84f85c88a355/coverage-7.6.12-cp310-cp310-win_amd64.whl", hash = "sha256:676f92141e3c5492d2a1596d52287d0d963df21bf5e55c8b03075a60e1ddf8aa", size = 211955 }, + { url = "https://files.pythonhosted.org/packages/64/2d/da78abbfff98468c91fd63a73cccdfa0e99051676ded8dd36123e3a2d4d5/coverage-7.6.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e18aafdfb3e9ec0d261c942d35bd7c28d031c5855dadb491d2723ba54f4c3015", size = 208464 }, + { url = "https://files.pythonhosted.org/packages/31/f2/c269f46c470bdabe83a69e860c80a82e5e76840e9f4bbd7f38f8cebbee2f/coverage-7.6.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66fe626fd7aa5982cdebad23e49e78ef7dbb3e3c2a5960a2b53632f1f703ea45", size = 208893 }, + { url = "https://files.pythonhosted.org/packages/47/63/5682bf14d2ce20819998a49c0deadb81e608a59eed64d6bc2191bc8046b9/coverage-7.6.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ef01d70198431719af0b1f5dcbefc557d44a190e749004042927b2a3fed0702", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/6a/b6/6b6631f1172d437e11067e1c2edfdb7238b65dff965a12bce3b6d1bf2be2/coverage-7.6.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e92ae5a289a4bc4c0aae710c0948d3c7892e20fd3588224ebe242039573bf0", size = 239230 }, + { url = "https://files.pythonhosted.org/packages/c7/01/9cd06cbb1be53e837e16f1b4309f6357e2dfcbdab0dd7cd3b1a50589e4e1/coverage-7.6.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e695df2c58ce526eeab11a2e915448d3eb76f75dffe338ea613c1201b33bab2f", size = 241013 }, + { url = "https://files.pythonhosted.org/packages/4b/26/56afefc03c30871326e3d99709a70d327ac1f33da383cba108c79bd71563/coverage-7.6.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d74c08e9aaef995f8c4ef6d202dbd219c318450fe2a76da624f2ebb9c8ec5d9f", size = 239750 }, + { url = "https://files.pythonhosted.org/packages/dd/ea/88a1ff951ed288f56aa561558ebe380107cf9132facd0b50bced63ba7238/coverage-7.6.12-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e995b3b76ccedc27fe4f477b349b7d64597e53a43fc2961db9d3fbace085d69d", size = 238462 }, + { url = "https://files.pythonhosted.org/packages/6e/d4/1d9404566f553728889409eff82151d515fbb46dc92cbd13b5337fa0de8c/coverage-7.6.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b1f097878d74fe51e1ddd1be62d8e3682748875b461232cf4b52ddc6e6db0bba", size = 239307 }, + { url = "https://files.pythonhosted.org/packages/12/c1/e453d3b794cde1e232ee8ac1d194fde8e2ba329c18bbf1b93f6f5eef606b/coverage-7.6.12-cp311-cp311-win32.whl", hash = "sha256:1f7ffa05da41754e20512202c866d0ebfc440bba3b0ed15133070e20bf5aeb5f", size = 211117 }, + { url = "https://files.pythonhosted.org/packages/d5/db/829185120c1686fa297294f8fcd23e0422f71070bf85ef1cc1a72ecb2930/coverage-7.6.12-cp311-cp311-win_amd64.whl", hash = "sha256:e216c5c45f89ef8971373fd1c5d8d1164b81f7f5f06bbf23c37e7908d19e8558", size = 212019 }, + { url = "https://files.pythonhosted.org/packages/e2/7f/4af2ed1d06ce6bee7eafc03b2ef748b14132b0bdae04388e451e4b2c529b/coverage-7.6.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b172f8e030e8ef247b3104902cc671e20df80163b60a203653150d2fc204d1ad", size = 208645 }, + { url = "https://files.pythonhosted.org/packages/dc/60/d19df912989117caa95123524d26fc973f56dc14aecdec5ccd7d0084e131/coverage-7.6.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:641dfe0ab73deb7069fb972d4d9725bf11c239c309ce694dd50b1473c0f641c3", size = 208898 }, + { url = "https://files.pythonhosted.org/packages/bd/10/fecabcf438ba676f706bf90186ccf6ff9f6158cc494286965c76e58742fa/coverage-7.6.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e549f54ac5f301e8e04c569dfdb907f7be71b06b88b5063ce9d6953d2d58574", size = 242987 }, + { url = "https://files.pythonhosted.org/packages/4c/53/4e208440389e8ea936f5f2b0762dcd4cb03281a7722def8e2bf9dc9c3d68/coverage-7.6.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:959244a17184515f8c52dcb65fb662808767c0bd233c1d8a166e7cf74c9ea985", size = 239881 }, + { url = "https://files.pythonhosted.org/packages/c4/47/2ba744af8d2f0caa1f17e7746147e34dfc5f811fb65fc153153722d58835/coverage-7.6.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bda1c5f347550c359f841d6614fb8ca42ae5cb0b74d39f8a1e204815ebe25750", size = 242142 }, + { url = "https://files.pythonhosted.org/packages/e9/90/df726af8ee74d92ee7e3bf113bf101ea4315d71508952bd21abc3fae471e/coverage-7.6.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ceeb90c3eda1f2d8c4c578c14167dbd8c674ecd7d38e45647543f19839dd6ea", size = 241437 }, + { url = "https://files.pythonhosted.org/packages/f6/af/995263fd04ae5f9cf12521150295bf03b6ba940d0aea97953bb4a6db3e2b/coverage-7.6.12-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f16f44025c06792e0fb09571ae454bcc7a3ec75eeb3c36b025eccf501b1a4c3", size = 239724 }, + { url = "https://files.pythonhosted.org/packages/1c/8e/5bb04f0318805e190984c6ce106b4c3968a9562a400180e549855d8211bd/coverage-7.6.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b076e625396e787448d27a411aefff867db2bffac8ed04e8f7056b07024eed5a", size = 241329 }, + { url = "https://files.pythonhosted.org/packages/9e/9d/fa04d9e6c3f6459f4e0b231925277cfc33d72dfab7fa19c312c03e59da99/coverage-7.6.12-cp312-cp312-win32.whl", hash = "sha256:00b2086892cf06c7c2d74983c9595dc511acca00665480b3ddff749ec4fb2a95", size = 211289 }, + { url = "https://files.pythonhosted.org/packages/53/40/53c7ffe3c0c3fff4d708bc99e65f3d78c129110d6629736faf2dbd60ad57/coverage-7.6.12-cp312-cp312-win_amd64.whl", hash = "sha256:7ae6eabf519bc7871ce117fb18bf14e0e343eeb96c377667e3e5dd12095e0288", size = 212079 }, + { url = "https://files.pythonhosted.org/packages/76/89/1adf3e634753c0de3dad2f02aac1e73dba58bc5a3a914ac94a25b2ef418f/coverage-7.6.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:488c27b3db0ebee97a830e6b5a3ea930c4a6e2c07f27a5e67e1b3532e76b9ef1", size = 208673 }, + { url = "https://files.pythonhosted.org/packages/ce/64/92a4e239d64d798535c5b45baac6b891c205a8a2e7c9cc8590ad386693dc/coverage-7.6.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d1095bbee1851269f79fd8e0c9b5544e4c00c0c24965e66d8cba2eb5bb535fd", size = 208945 }, + { url = "https://files.pythonhosted.org/packages/b4/d0/4596a3ef3bca20a94539c9b1e10fd250225d1dec57ea78b0867a1cf9742e/coverage-7.6.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0533adc29adf6a69c1baa88c3d7dbcaadcffa21afbed3ca7a225a440e4744bf9", size = 242484 }, + { url = "https://files.pythonhosted.org/packages/1c/ef/6fd0d344695af6718a38d0861408af48a709327335486a7ad7e85936dc6e/coverage-7.6.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53c56358d470fa507a2b6e67a68fd002364d23c83741dbc4c2e0680d80ca227e", size = 239525 }, + { url = "https://files.pythonhosted.org/packages/0c/4b/373be2be7dd42f2bcd6964059fd8fa307d265a29d2b9bcf1d044bcc156ed/coverage-7.6.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64cbb1a3027c79ca6310bf101014614f6e6e18c226474606cf725238cf5bc2d4", size = 241545 }, + { url = "https://files.pythonhosted.org/packages/a6/7d/0e83cc2673a7790650851ee92f72a343827ecaaea07960587c8f442b5cd3/coverage-7.6.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:79cac3390bfa9836bb795be377395f28410811c9066bc4eefd8015258a7578c6", size = 241179 }, + { url = "https://files.pythonhosted.org/packages/ff/8c/566ea92ce2bb7627b0900124e24a99f9244b6c8c92d09ff9f7633eb7c3c8/coverage-7.6.12-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b148068e881faa26d878ff63e79650e208e95cf1c22bd3f77c3ca7b1d9821a3", size = 239288 }, + { url = "https://files.pythonhosted.org/packages/7d/e4/869a138e50b622f796782d642c15fb5f25a5870c6d0059a663667a201638/coverage-7.6.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8bec2ac5da793c2685ce5319ca9bcf4eee683b8a1679051f8e6ec04c4f2fd7dc", size = 241032 }, + { url = "https://files.pythonhosted.org/packages/ae/28/a52ff5d62a9f9e9fe9c4f17759b98632edd3a3489fce70154c7d66054dd3/coverage-7.6.12-cp313-cp313-win32.whl", hash = "sha256:200e10beb6ddd7c3ded322a4186313d5ca9e63e33d8fab4faa67ef46d3460af3", size = 211315 }, + { url = "https://files.pythonhosted.org/packages/bc/17/ab849b7429a639f9722fa5628364c28d675c7ff37ebc3268fe9840dda13c/coverage-7.6.12-cp313-cp313-win_amd64.whl", hash = "sha256:2b996819ced9f7dbb812c701485d58f261bef08f9b85304d41219b1496b591ef", size = 212099 }, + { url = "https://files.pythonhosted.org/packages/d2/1c/b9965bf23e171d98505eb5eb4fb4d05c44efd256f2e0f19ad1ba8c3f54b0/coverage-7.6.12-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:299cf973a7abff87a30609879c10df0b3bfc33d021e1adabc29138a48888841e", size = 209511 }, + { url = "https://files.pythonhosted.org/packages/57/b3/119c201d3b692d5e17784fee876a9a78e1b3051327de2709392962877ca8/coverage-7.6.12-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4b467a8c56974bf06e543e69ad803c6865249d7a5ccf6980457ed2bc50312703", size = 209729 }, + { url = "https://files.pythonhosted.org/packages/52/4e/a7feb5a56b266304bc59f872ea07b728e14d5a64f1ad3a2cc01a3259c965/coverage-7.6.12-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2458f275944db8129f95d91aee32c828a408481ecde3b30af31d552c2ce284a0", size = 253988 }, + { url = "https://files.pythonhosted.org/packages/65/19/069fec4d6908d0dae98126aa7ad08ce5130a6decc8509da7740d36e8e8d2/coverage-7.6.12-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a9d8be07fb0832636a0f72b80d2a652fe665e80e720301fb22b191c3434d924", size = 249697 }, + { url = "https://files.pythonhosted.org/packages/1c/da/5b19f09ba39df7c55f77820736bf17bbe2416bbf5216a3100ac019e15839/coverage-7.6.12-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14d47376a4f445e9743f6c83291e60adb1b127607a3618e3185bbc8091f0467b", size = 252033 }, + { url = "https://files.pythonhosted.org/packages/1e/89/4c2750df7f80a7872267f7c5fe497c69d45f688f7b3afe1297e52e33f791/coverage-7.6.12-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b95574d06aa9d2bd6e5cc35a5bbe35696342c96760b69dc4287dbd5abd4ad51d", size = 251535 }, + { url = "https://files.pythonhosted.org/packages/78/3b/6d3ae3c1cc05f1b0460c51e6f6dcf567598cbd7c6121e5ad06643974703c/coverage-7.6.12-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ecea0c38c9079570163d663c0433a9af4094a60aafdca491c6a3d248c7432827", size = 249192 }, + { url = "https://files.pythonhosted.org/packages/6e/8e/c14a79f535ce41af7d436bbad0d3d90c43d9e38ec409b4770c894031422e/coverage-7.6.12-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2251fabcfee0a55a8578a9d29cecfee5f2de02f11530e7d5c5a05859aa85aee9", size = 250627 }, + { url = "https://files.pythonhosted.org/packages/cb/79/b7cee656cfb17a7f2c1b9c3cee03dd5d8000ca299ad4038ba64b61a9b044/coverage-7.6.12-cp313-cp313t-win32.whl", hash = "sha256:eb5507795caabd9b2ae3f1adc95f67b1104971c22c624bb354232d65c4fc90b3", size = 212033 }, + { url = "https://files.pythonhosted.org/packages/b6/c3/f7aaa3813f1fa9a4228175a7bd368199659d392897e184435a3b66408dd3/coverage-7.6.12-cp313-cp313t-win_amd64.whl", hash = "sha256:f60a297c3987c6c02ffb29effc70eadcbb412fe76947d394a1091a3615948e2f", size = 213240 }, + { url = "https://files.pythonhosted.org/packages/7a/7f/05818c62c7afe75df11e0233bd670948d68b36cdbf2a339a095bc02624a8/coverage-7.6.12-pp39.pp310-none-any.whl", hash = "sha256:7e39e845c4d764208e7b8f6a21c541ade741e2c41afabdfa1caa28687a3c98cf", size = 200558 }, + { url = "https://files.pythonhosted.org/packages/fb/b2/f655700e1024dec98b10ebaafd0cedbc25e40e4abe62a3c8e2ceef4f8f0a/coverage-7.6.12-py3-none-any.whl", hash = "sha256:eb8668cfbc279a536c633137deeb9435d2962caec279c3f8cf8b91fff6ff8953", size = 200552 }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + +[[package]] +name = "debugpy" +version = "1.8.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/25/c74e337134edf55c4dfc9af579eccb45af2393c40960e2795a94351e8140/debugpy-1.8.12.tar.gz", hash = "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce", size = 1641122 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/19/dd58334c0a1ec07babf80bf29fb8daf1a7ca4c1a3bbe61548e40616ac087/debugpy-1.8.12-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a", size = 2076091 }, + { url = "https://files.pythonhosted.org/packages/4c/37/bde1737da15f9617d11ab7b8d5267165f1b7dae116b2585a6643e89e1fa2/debugpy-1.8.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45", size = 3560717 }, + { url = "https://files.pythonhosted.org/packages/d9/ca/bc67f5a36a7de072908bc9e1156c0f0b272a9a2224cf21540ab1ffd71a1f/debugpy-1.8.12-cp310-cp310-win32.whl", hash = "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c", size = 5180672 }, + { url = "https://files.pythonhosted.org/packages/c1/b9/e899c0a80dfa674dbc992f36f2b1453cd1ee879143cdb455bc04fce999da/debugpy-1.8.12-cp310-cp310-win_amd64.whl", hash = "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9", size = 5212702 }, + { url = "https://files.pythonhosted.org/packages/af/9f/5b8af282253615296264d4ef62d14a8686f0dcdebb31a669374e22fff0a4/debugpy-1.8.12-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5", size = 2174643 }, + { url = "https://files.pythonhosted.org/packages/ef/31/f9274dcd3b0f9f7d1e60373c3fa4696a585c55acb30729d313bb9d3bcbd1/debugpy-1.8.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7", size = 3133457 }, + { url = "https://files.pythonhosted.org/packages/ab/ca/6ee59e9892e424477e0c76e3798046f1fd1288040b927319c7a7b0baa484/debugpy-1.8.12-cp311-cp311-win32.whl", hash = "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb", size = 5106220 }, + { url = "https://files.pythonhosted.org/packages/d5/1a/8ab508ab05ede8a4eae3b139bbc06ea3ca6234f9e8c02713a044f253be5e/debugpy-1.8.12-cp311-cp311-win_amd64.whl", hash = "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1", size = 5130481 }, + { url = "https://files.pythonhosted.org/packages/ba/e6/0f876ecfe5831ebe4762b19214364753c8bc2b357d28c5d739a1e88325c7/debugpy-1.8.12-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498", size = 2500846 }, + { url = "https://files.pythonhosted.org/packages/19/64/33f41653a701f3cd2cbff8b41ebaad59885b3428b5afd0d93d16012ecf17/debugpy-1.8.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06", size = 4222181 }, + { url = "https://files.pythonhosted.org/packages/32/a6/02646cfe50bfacc9b71321c47dc19a46e35f4e0aceea227b6d205e900e34/debugpy-1.8.12-cp312-cp312-win32.whl", hash = "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d", size = 5227017 }, + { url = "https://files.pythonhosted.org/packages/da/a6/10056431b5c47103474312cf4a2ec1001f73e0b63b1216706d5fef2531eb/debugpy-1.8.12-cp312-cp312-win_amd64.whl", hash = "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969", size = 5267555 }, + { url = "https://files.pythonhosted.org/packages/cf/4d/7c3896619a8791effd5d8c31f0834471fc8f8fb3047ec4f5fc69dd1393dd/debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f", size = 2485246 }, + { url = "https://files.pythonhosted.org/packages/99/46/bc6dcfd7eb8cc969a5716d858e32485eb40c72c6a8dc88d1e3a4d5e95813/debugpy-1.8.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9", size = 4218616 }, + { url = "https://files.pythonhosted.org/packages/03/dd/d7fcdf0381a9b8094da1f6a1c9f19fed493a4f8576a2682349b3a8b20ec7/debugpy-1.8.12-cp313-cp313-win32.whl", hash = "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180", size = 5226540 }, + { url = "https://files.pythonhosted.org/packages/25/bd/ecb98f5b5fc7ea0bfbb3c355bc1dd57c198a28780beadd1e19915bf7b4d9/debugpy-1.8.12-cp313-cp313-win_amd64.whl", hash = "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c", size = 5267134 }, + { url = "https://files.pythonhosted.org/packages/38/c4/5120ad36405c3008f451f94b8f92ef1805b1e516f6ff870f331ccb3c4cc0/debugpy-1.8.12-py2.py3-none-any.whl", hash = "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6", size = 5229490 }, +] + +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, +] + [[package]] name = "deprecation" version = "2.1.0" @@ -288,6 +570,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, ] +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924 }, +] + [[package]] name = "filelock" version = "3.17.0" @@ -322,6 +622,24 @@ test = [ { name = "tox" }, ] +[package.dev-dependencies] +docs = [ + { name = "myst-parser" }, + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, + { name = "sphinx-automodapi" }, +] +test = [ + { name = "ipykernel" }, + { name = "jupyter-firefly-extensions" }, + { name = "pytest" }, + { name = "pytest-container" }, + { name = "pytest-cov" }, + { name = "pytest-doctestplus" }, + { name = "pytest-xdist" }, + { name = "tox" }, +] + [package.metadata] requires-dist = [ { name = "astropy", specifier = ">=6.1.7" }, @@ -338,6 +656,33 @@ requires-dist = [ ] provides-extras = ["docs", "test"] +[package.metadata.requires-dev] +docs = [ + { name = "myst-parser", specifier = ">=4.0.1" }, + { name = "pydata-sphinx-theme", specifier = ">=0.16.1" }, + { name = "sphinx", specifier = "~=7.1.0" }, + { name = "sphinx-automodapi", specifier = ">=0.18.0" }, +] +test = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "jupyter-firefly-extensions", specifier = ">=4.3.1" }, + { name = "pytest", specifier = ">=8.3.4" }, + { name = "pytest-container", specifier = ">=0.4.3" }, + { name = "pytest-cov", specifier = ">=6.0.0" }, + { name = "pytest-doctestplus", specifier = ">=1.4.0" }, + { name = "pytest-xdist", specifier = ">=3.6.1" }, + { name = "tox", specifier = ">=4.24.1" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, +] + [[package]] name = "idna" version = "3.10" @@ -365,6 +710,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { 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/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "8.32.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/80/4d2a072e0db7d250f134bc11676517299264ebe16d62a8619d49a78ced73/ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251", size = 5507441 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e1/f4474a7ecdb7745a820f6f6039dc43c66add40f1bcc66485607d93571af6/ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa", size = 825524 }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, +] + [[package]] name = "jinja2" version = "3.1.5" @@ -377,6 +792,168 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, ] +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.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/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +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/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430 }, +] + +[[package]] +name = "jupyter-firefly-extensions" +version = "4.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "firefly-client" }, + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/68/2c80a7aec7ba0ceb9d004890a6291b20b93a1cdc7a913a527b6235a7d94b/jupyter_firefly_extensions-4.3.1.tar.gz", hash = "sha256:f40d311a415cdb79edae6f1e20f7a3bfd80b2b5200ea7173890b145267e34285", size = 833747 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/0a/5d8de1db9f4543f463a2e8a0eab3fbb149350258474480d7f57f1c6efcf0/jupyter_firefly_extensions-4.3.1-py3-none-any.whl", hash = "sha256:7373e5cbeebffd18c6ec2c599a37a2ee7b647149889fab5fa099189b90cc0170", size = 1643834 }, +] + +[[package]] +name = "jupyter-server" +version = "2.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "overrides" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/8c/df09d4ab646141f130f9977b32b206ba8615d1969b2eba6a2e84b7f89137/jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084", size = 725227 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/a2/89eeaf0bb954a123a909859fa507fa86f96eb61b62dc30667b60dbd5fdaf/jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", size = 385826 }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -447,6 +1024,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -468,6 +1057,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] +[[package]] +name = "mistune" +version = "3.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/1d/6b2b634e43bacc3239006e61800676aa6c41ac1836b2c57497ed27a7310b/mistune-3.1.1.tar.gz", hash = "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c", size = 94645 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/02/c66bdfdadbb021adb642ca4e8a5ed32ada0b4a3e4b39c5d076d19543452f/mistune-3.1.1-py3-none-any.whl", hash = "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a", size = 53696 }, +] + [[package]] name = "myst-parser" version = "4.0.1" @@ -485,6 +1086,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579 }, ] +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +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/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + [[package]] name = "numpy" version = "2.2.3" @@ -547,6 +1212,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, ] +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, +] + [[package]] name = "packaging" version = "24.2" @@ -556,6 +1230,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, +] + +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + [[package]] name = "platformdirs" version = "4.3.6" @@ -574,6 +1278,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] +[[package]] +name = "prometheus-client" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.50" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -664,6 +1431,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/04/04/99e05194194e3f21bc627e700e1edc1aea91f1161bbb32728bea5c73c128/pytest_container-0.4.3-py3-none-any.whl", hash = "sha256:94be6bd43ece7a6a9fa7781d8497f96db2f7d77864addbe2d57ee59a41649721", size = 44127 }, ] +[[package]] +name = "pytest-cov" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", extra = ["toml"] }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/45/9b538de8cef30e17c7b45ef42f538a94889ed6a16f2387a6c89e73220651/pytest-cov-6.0.0.tar.gz", hash = "sha256:fde0b595ca248bb8e2d76f020b465f3b107c9632e6a1d1705f17834c89dcadc0", size = 66945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/3b/48e79f2cd6a61dbbd4807b4ed46cb564b4fd50a76166b1c4ea5c1d9e2371/pytest_cov-6.0.0-py3-none-any.whl", hash = "sha256:eee6f1b9e61008bd34975a4d5bab25801eb31898b032dd55addc93e96fcaaa35", size = 22949 }, +] + +[[package]] +name = "pytest-doctestplus" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/e5/97c4bc17e93d5caf6b37ebab0bdfd668c1c62d575e6e1e5040bfa759b4f2/pytest_doctestplus-1.4.0.tar.gz", hash = "sha256:df83832b1d11288572df2ee4c7cccdb421d812b8038a658bb514c9c62bdbd626", size = 47566 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/08/0e0e38a6046f91ad6ae352c0639c1b6dd90e2cd53ab2d2282d1d231535fb/pytest_doctestplus-1.4.0-py3-none-any.whl", hash = "sha256:cfbae130ec90d4a2831819bbbfd097121b8e55f1e4d20a47ea992e4eaad2539a", size = 25236 }, +] + [[package]] name = "pytest-testinfra" version = "10.1.1" @@ -689,6 +1482,59 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, ] +[[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 } +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 }, +] + +[[package]] +name = "python-json-logger" +version = "3.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/c4/358cd13daa1d912ef795010897a483ab2f0b41c9ea1b35235a8b2f7d15a7/python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008", size = 16287 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/72/2f30cf26664fcfa0bd8ec5ee62ec90c03bd485e4a294d92aabc76c5203a5/python_json_logger-3.2.1-py3-none-any.whl", hash = "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090", size = 14924 }, +] + +[[package]] +name = "pywin32" +version = "308" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028 }, + { url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484 }, + { url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454 }, + { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, + { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, + { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, + { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, + { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, + { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, + { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, + { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, +] + +[[package]] +name = "pywinpty" +version = "2.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/7c/917f9c4681bb8d34bfbe0b79d36bbcd902651aeab48790df3d30ba0202fb/pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2", size = 29017 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/b7/855db919ae526d2628f3f2e6c281c4cdff7a9a8af51bb84659a9f07b1861/pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e", size = 1405161 }, + { url = "https://files.pythonhosted.org/packages/5e/ac/6884dcb7108af66ad53f73ef4dad096e768c9203a6e6ce5e6b0c4a46e238/pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca", size = 1405249 }, + { url = "https://files.pythonhosted.org/packages/88/e5/9714def18c3a411809771a3fbcec70bffa764b9675afb00048a620fca604/pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc", size = 1405243 }, + { url = "https://files.pythonhosted.org/packages/fb/16/2ab7b3b7f55f3c6929e5f629e1a68362981e4e5fed592a2ed1cb4b4914a5/pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408", size = 1405020 }, + { url = "https://files.pythonhosted.org/packages/7c/16/edef3515dd2030db2795dbfbe392232c7a0f3dc41b98e92b38b42ba497c7/pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901", size = 1404151 }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -733,6 +1579,93 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] +[[package]] +name = "pyzmq" +version = "26.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/e3/8d0382cb59feb111c252b54e8728257416a38ffcb2243c4e4775a3c990fe/pyzmq-26.2.1.tar.gz", hash = "sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca", size = 278433 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/3d/c2d9d46c033d1b51692ea49a22439f7f66d91d5c938e8b5c56ed7a2151c2/pyzmq-26.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb", size = 1345451 }, + { url = "https://files.pythonhosted.org/packages/0e/df/4754a8abcdeef280651f9bb51446c47659910940b392a66acff7c37f5cef/pyzmq-26.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641", size = 942766 }, + { url = "https://files.pythonhosted.org/packages/74/da/e6053a3b13c912eded6c2cdeee22ff3a4c33820d17f9eb24c7b6e957ffe7/pyzmq-26.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257", size = 678488 }, + { url = "https://files.pythonhosted.org/packages/9e/50/614934145244142401ca174ca81071777ab93aa88173973ba0154f491e09/pyzmq-26.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff", size = 917115 }, + { url = "https://files.pythonhosted.org/packages/80/2b/ebeb7bc4fc8e9e61650b2e09581597355a4341d413fa9b2947d7a6558119/pyzmq-26.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24", size = 874162 }, + { url = "https://files.pythonhosted.org/packages/79/48/93210621c331ad16313dc2849801411fbae10d91d878853933f2a85df8e7/pyzmq-26.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459", size = 874180 }, + { url = "https://files.pythonhosted.org/packages/f0/8b/40924b4d8e33bfdd54c1970fb50f327e39b90b902f897cf09b30b2e9ac48/pyzmq-26.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c", size = 1208139 }, + { url = "https://files.pythonhosted.org/packages/c8/b2/82d6675fc89bd965eae13c45002c792d33f06824589844b03f8ea8fc6d86/pyzmq-26.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e", size = 1520666 }, + { url = "https://files.pythonhosted.org/packages/9d/e2/5ff15f2d3f920dcc559d477bd9bb3faacd6d79fcf7c5448e585c78f84849/pyzmq-26.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3", size = 1420056 }, + { url = "https://files.pythonhosted.org/packages/40/a2/f9bbeccf7f75aa0d8963e224e5730abcefbf742e1f2ae9ea60fd9d6ff72b/pyzmq-26.2.1-cp310-cp310-win32.whl", hash = "sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa", size = 583874 }, + { url = "https://files.pythonhosted.org/packages/56/b1/44f513135843272f0e12f5aebf4af35839e2a88eb45411f2c8c010d8c856/pyzmq-26.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473", size = 647367 }, + { url = "https://files.pythonhosted.org/packages/27/9c/1bef14a37b02d651a462811bbdb1390b61cd4a5b5e95cbd7cc2d60ef848c/pyzmq-26.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594", size = 561784 }, + { url = "https://files.pythonhosted.org/packages/b9/03/5ecc46a6ed5971299f5c03e016ca637802d8660e44392bea774fb7797405/pyzmq-26.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a", size = 1346032 }, + { url = "https://files.pythonhosted.org/packages/40/51/48fec8f990ee644f461ff14c8fe5caa341b0b9b3a0ad7544f8ef17d6f528/pyzmq-26.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a", size = 943324 }, + { url = "https://files.pythonhosted.org/packages/c1/f4/f322b389727c687845e38470b48d7a43c18a83f26d4d5084603c6c3f79ca/pyzmq-26.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454", size = 678418 }, + { url = "https://files.pythonhosted.org/packages/a8/df/2834e3202533bd05032d83e02db7ac09fa1be853bbef59974f2b2e3a8557/pyzmq-26.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99", size = 915466 }, + { url = "https://files.pythonhosted.org/packages/b5/e2/45c0f6e122b562cb8c6c45c0dcac1160a4e2207385ef9b13463e74f93031/pyzmq-26.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4", size = 873347 }, + { url = "https://files.pythonhosted.org/packages/de/b9/3e0fbddf8b87454e914501d368171466a12550c70355b3844115947d68ea/pyzmq-26.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa", size = 874545 }, + { url = "https://files.pythonhosted.org/packages/1f/1c/1ee41d6e10b2127263b1994bc53b9e74ece015b0d2c0a30e0afaf69b78b2/pyzmq-26.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f", size = 1208630 }, + { url = "https://files.pythonhosted.org/packages/3d/a9/50228465c625851a06aeee97c74f253631f509213f979166e83796299c60/pyzmq-26.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba", size = 1519568 }, + { url = "https://files.pythonhosted.org/packages/c6/f2/6360b619e69da78863c2108beb5196ae8b955fe1e161c0b886b95dc6b1ac/pyzmq-26.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd", size = 1419677 }, + { url = "https://files.pythonhosted.org/packages/da/d5/f179da989168f5dfd1be8103ef508ade1d38a8078dda4f10ebae3131a490/pyzmq-26.2.1-cp311-cp311-win32.whl", hash = "sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7", size = 582682 }, + { url = "https://files.pythonhosted.org/packages/60/50/e5b2e9de3ffab73ff92bee736216cf209381081fa6ab6ba96427777d98b1/pyzmq-26.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1", size = 648128 }, + { url = "https://files.pythonhosted.org/packages/d9/fe/7bb93476dd8405b0fc9cab1fd921a08bd22d5e3016aa6daea1a78d54129b/pyzmq-26.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7", size = 562465 }, + { url = "https://files.pythonhosted.org/packages/9c/b9/260a74786f162c7f521f5f891584a51d5a42fd15f5dcaa5c9226b2865fcc/pyzmq-26.2.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3", size = 1348495 }, + { url = "https://files.pythonhosted.org/packages/bf/73/8a0757e4b68f5a8ccb90ddadbb76c6a5f880266cdb18be38c99bcdc17aaa/pyzmq-26.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e", size = 945035 }, + { url = "https://files.pythonhosted.org/packages/cf/de/f02ec973cd33155bb772bae33ace774acc7cc71b87b25c4829068bec35de/pyzmq-26.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8", size = 671213 }, + { url = "https://files.pythonhosted.org/packages/d1/80/8fc583085f85ac91682744efc916888dd9f11f9f75a31aef1b78a5486c6c/pyzmq-26.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09", size = 908750 }, + { url = "https://files.pythonhosted.org/packages/c3/25/0b4824596f261a3cc512ab152448b383047ff5f143a6906a36876415981c/pyzmq-26.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da", size = 865416 }, + { url = "https://files.pythonhosted.org/packages/a1/d1/6fda77a034d02034367b040973fd3861d945a5347e607bd2e98c99f20599/pyzmq-26.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435", size = 865922 }, + { url = "https://files.pythonhosted.org/packages/ad/81/48f7fd8a71c427412e739ce576fc1ee14f3dc34527ca9b0076e471676183/pyzmq-26.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a", size = 1201526 }, + { url = "https://files.pythonhosted.org/packages/c7/d8/818f15c6ef36b5450e435cbb0d3a51599fc884a5d2b27b46b9c00af68ef1/pyzmq-26.2.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4", size = 1512808 }, + { url = "https://files.pythonhosted.org/packages/d9/c4/b3edb7d0ae82ad6fb1a8cdb191a4113c427a01e85139906f3b655b07f4f8/pyzmq-26.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e", size = 1411836 }, + { url = "https://files.pythonhosted.org/packages/69/1c/151e3d42048f02cc5cd6dfc241d9d36b38375b4dee2e728acb5c353a6d52/pyzmq-26.2.1-cp312-cp312-win32.whl", hash = "sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a", size = 581378 }, + { url = "https://files.pythonhosted.org/packages/b6/b9/d59a7462848aaab7277fddb253ae134a570520115d80afa85e952287e6bc/pyzmq-26.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13", size = 643737 }, + { url = "https://files.pythonhosted.org/packages/55/09/f37e707937cce328944c1d57e5e50ab905011d35252a0745c4f7e5822a76/pyzmq-26.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5", size = 558303 }, + { url = "https://files.pythonhosted.org/packages/4f/2e/fa7a91ce349975971d6aa925b4c7e1a05abaae99b97ade5ace758160c43d/pyzmq-26.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23", size = 942331 }, + { url = "https://files.pythonhosted.org/packages/64/2b/1f10b34b6dc7ff4b40f668ea25ba9b8093ce61d874c784b90229b367707b/pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be", size = 1345831 }, + { url = "https://files.pythonhosted.org/packages/4c/8d/34884cbd4a8ec050841b5fb58d37af136766a9f95b0b2634c2971deb09da/pyzmq-26.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399", size = 670773 }, + { url = "https://files.pythonhosted.org/packages/0f/f4/d4becfcf9e416ad2564f18a6653f7c6aa917da08df5c3760edb0baa1c863/pyzmq-26.2.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9", size = 908836 }, + { url = "https://files.pythonhosted.org/packages/07/fa/ab105f1b86b85cb2e821239f1d0900fccd66192a91d97ee04661b5436b4d/pyzmq-26.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab", size = 865369 }, + { url = "https://files.pythonhosted.org/packages/c9/48/15d5f415504572dd4b92b52db5de7a5befc76bb75340ba9f36f71306a66d/pyzmq-26.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce", size = 865676 }, + { url = "https://files.pythonhosted.org/packages/7e/35/2d91bcc7ccbb56043dd4d2c1763f24a8de5f05e06a134f767a7fb38e149c/pyzmq-26.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a", size = 1201457 }, + { url = "https://files.pythonhosted.org/packages/6d/bb/aa7c5119307a5762b8dca6c9db73e3ab4bccf32b15d7c4f376271ff72b2b/pyzmq-26.2.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9", size = 1513035 }, + { url = "https://files.pythonhosted.org/packages/4f/4c/527e6650c2fccec7750b783301329c8a8716d59423818afb67282304ce5a/pyzmq-26.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad", size = 1411881 }, + { url = "https://files.pythonhosted.org/packages/89/9f/e4412ea1b3e220acc21777a5edba8885856403d29c6999aaf00a9459eb03/pyzmq-26.2.1-cp313-cp313-win32.whl", hash = "sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb", size = 581354 }, + { url = "https://files.pythonhosted.org/packages/55/cd/f89dd3e9fc2da0d1619a82c4afb600c86b52bc72d7584953d460bc8d5027/pyzmq-26.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf", size = 643560 }, + { url = "https://files.pythonhosted.org/packages/a7/99/5de4f8912860013f1116f818a0047659bc20d71d1bc1d48f874bdc2d7b9c/pyzmq-26.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce", size = 558037 }, + { url = "https://files.pythonhosted.org/packages/06/0b/63b6d7a2f07a77dbc9768c6302ae2d7518bed0c6cee515669ca0d8ec743e/pyzmq-26.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e", size = 938580 }, + { url = "https://files.pythonhosted.org/packages/85/38/e5e2c3ffa23ea5f95f1c904014385a55902a11a67cd43c10edf61a653467/pyzmq-26.2.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891", size = 1339670 }, + { url = "https://files.pythonhosted.org/packages/d2/87/da5519ed7f8b31e4beee8f57311ec02926822fe23a95120877354cd80144/pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6", size = 660983 }, + { url = "https://files.pythonhosted.org/packages/f6/e8/1ca6a2d59562e04d326a026c9e3f791a6f1a276ebde29da478843a566fdb/pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a", size = 896509 }, + { url = "https://files.pythonhosted.org/packages/5c/e5/0b4688f7c74bea7e4f1e920da973fcd7d20175f4f1181cb9b692429c6bb9/pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3", size = 853196 }, + { url = "https://files.pythonhosted.org/packages/8f/35/c17241da01195001828319e98517683dad0ac4df6fcba68763d61b630390/pyzmq-26.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e", size = 855133 }, + { url = "https://files.pythonhosted.org/packages/d2/14/268ee49bbecc3f72e225addeac7f0e2bd5808747b78c7bf7f87ed9f9d5a8/pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7", size = 1191612 }, + { url = "https://files.pythonhosted.org/packages/5e/02/6394498620b1b4349b95c534f3ebc3aef95f39afbdced5ed7ee315c49c14/pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8", size = 1500824 }, + { url = "https://files.pythonhosted.org/packages/17/fc/b79f0b72891cbb9917698add0fede71dfb64e83fa3481a02ed0e78c34be7/pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460", size = 1399943 }, + { url = "https://files.pythonhosted.org/packages/65/d1/e630a75cfb2534574a1258fda54d02f13cf80b576d4ce6d2aa478dc67829/pyzmq-26.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d", size = 847743 }, + { url = "https://files.pythonhosted.org/packages/27/df/f94a711b4f6c4b41e227f9a938103f52acf4c2e949d91cbc682495a48155/pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99", size = 570991 }, + { url = "https://files.pythonhosted.org/packages/bf/08/0c6f97fb3c9dbfa23382f0efaf8f9aa1396a08a3358974eaae3ee659ed5c/pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c", size = 799664 }, + { url = "https://files.pythonhosted.org/packages/05/14/f4d4fd8bb8988c667845734dd756e9ee65b9a17a010d5f288dfca14a572d/pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53", size = 758156 }, + { url = "https://files.pythonhosted.org/packages/e3/fe/72e7e166bda3885810bee7b23049133e142f7c80c295bae02c562caeea16/pyzmq-26.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9", size = 556563 }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +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/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + [[package]] name = "requests" version = "2.32.3" @@ -748,6 +1681,139 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, +] + +[[package]] +name = "rpds-py" +version = "0.22.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/80/cce854d0921ff2f0a9fa831ba3ad3c65cee3a46711addf39a2af52df2cfd/rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", size = 26771 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/2a/ead1d09e57449b99dcc190d8d2323e3a167421d8f8fdf0f217c6f6befe47/rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967", size = 359514 }, + { url = "https://files.pythonhosted.org/packages/8f/7e/1254f406b7793b586c68e217a6a24ec79040f85e030fff7e9049069284f4/rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37", size = 349031 }, + { url = "https://files.pythonhosted.org/packages/aa/da/17c6a2c73730d426df53675ff9cc6653ac7a60b6438d03c18e1c822a576a/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24", size = 381485 }, + { url = "https://files.pythonhosted.org/packages/aa/13/2dbacd820466aa2a3c4b747afb18d71209523d353cf865bf8f4796c969ea/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff", size = 386794 }, + { url = "https://files.pythonhosted.org/packages/6d/62/96905d0a35ad4e4bc3c098b2f34b2e7266e211d08635baa690643d2227be/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c", size = 423523 }, + { url = "https://files.pythonhosted.org/packages/eb/1b/d12770f2b6a9fc2c3ec0d810d7d440f6d465ccd8b7f16ae5385952c28b89/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e", size = 446695 }, + { url = "https://files.pythonhosted.org/packages/4d/cf/96f1fd75512a017f8e07408b6d5dbeb492d9ed46bfe0555544294f3681b3/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec", size = 381959 }, + { url = "https://files.pythonhosted.org/packages/ab/f0/d1c5b501c8aea85aeb938b555bfdf7612110a2f8cdc21ae0482c93dd0c24/rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c", size = 410420 }, + { url = "https://files.pythonhosted.org/packages/33/3b/45b6c58fb6aad5a569ae40fb890fc494c6b02203505a5008ee6dc68e65f7/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09", size = 557620 }, + { url = "https://files.pythonhosted.org/packages/83/62/3fdd2d3d47bf0bb9b931c4c73036b4ab3ec77b25e016ae26fab0f02be2af/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00", size = 584202 }, + { url = "https://files.pythonhosted.org/packages/04/f2/5dced98b64874b84ca824292f9cee2e3f30f3bcf231d15a903126684f74d/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf", size = 552787 }, + { url = "https://files.pythonhosted.org/packages/67/13/2273dea1204eda0aea0ef55145da96a9aa28b3f88bb5c70e994f69eda7c3/rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652", size = 220088 }, + { url = "https://files.pythonhosted.org/packages/4e/80/8c8176b67ad7f4a894967a7a4014ba039626d96f1d4874d53e409b58d69f/rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8", size = 231737 }, + { url = "https://files.pythonhosted.org/packages/15/ad/8d1ddf78f2805a71253fcd388017e7b4a0615c22c762b6d35301fef20106/rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f", size = 359773 }, + { url = "https://files.pythonhosted.org/packages/c8/75/68c15732293a8485d79fe4ebe9045525502a067865fa4278f178851b2d87/rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a", size = 349214 }, + { url = "https://files.pythonhosted.org/packages/3c/4c/7ce50f3070083c2e1b2bbd0fb7046f3da55f510d19e283222f8f33d7d5f4/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5", size = 380477 }, + { url = "https://files.pythonhosted.org/packages/9a/e9/835196a69cb229d5c31c13b8ae603bd2da9a6695f35fe4270d398e1db44c/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb", size = 386171 }, + { url = "https://files.pythonhosted.org/packages/f9/8e/33fc4eba6683db71e91e6d594a2cf3a8fbceb5316629f0477f7ece5e3f75/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2", size = 422676 }, + { url = "https://files.pythonhosted.org/packages/37/47/2e82d58f8046a98bb9497a8319604c92b827b94d558df30877c4b3c6ccb3/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0", size = 446152 }, + { url = "https://files.pythonhosted.org/packages/e1/78/79c128c3e71abbc8e9739ac27af11dc0f91840a86fce67ff83c65d1ba195/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1", size = 381300 }, + { url = "https://files.pythonhosted.org/packages/c9/5b/2e193be0e8b228c1207f31fa3ea79de64dadb4f6a4833111af8145a6bc33/rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d", size = 409636 }, + { url = "https://files.pythonhosted.org/packages/c2/3f/687c7100b762d62186a1c1100ffdf99825f6fa5ea94556844bbbd2d0f3a9/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648", size = 556708 }, + { url = "https://files.pythonhosted.org/packages/8c/a2/c00cbc4b857e8b3d5e7f7fc4c81e23afd8c138b930f4f3ccf9a41a23e9e4/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74", size = 583554 }, + { url = "https://files.pythonhosted.org/packages/d0/08/696c9872cf56effdad9ed617ac072f6774a898d46b8b8964eab39ec562d2/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a", size = 552105 }, + { url = "https://files.pythonhosted.org/packages/18/1f/4df560be1e994f5adf56cabd6c117e02de7c88ee238bb4ce03ed50da9d56/rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64", size = 220199 }, + { url = "https://files.pythonhosted.org/packages/b8/1b/c29b570bc5db8237553002788dc734d6bd71443a2ceac2a58202ec06ef12/rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c", size = 231775 }, + { url = "https://files.pythonhosted.org/packages/75/47/3383ee3bd787a2a5e65a9b9edc37ccf8505c0a00170e3a5e6ea5fbcd97f7/rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", size = 352334 }, + { url = "https://files.pythonhosted.org/packages/40/14/aa6400fa8158b90a5a250a77f2077c0d0cd8a76fce31d9f2b289f04c6dec/rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", size = 342111 }, + { url = "https://files.pythonhosted.org/packages/7d/06/395a13bfaa8a28b302fb433fb285a67ce0ea2004959a027aea8f9c52bad4/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", size = 384286 }, + { url = "https://files.pythonhosted.org/packages/43/52/d8eeaffab047e6b7b7ef7f00d5ead074a07973968ffa2d5820fa131d7852/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e", size = 391739 }, + { url = "https://files.pythonhosted.org/packages/83/31/52dc4bde85c60b63719610ed6f6d61877effdb5113a72007679b786377b8/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", size = 427306 }, + { url = "https://files.pythonhosted.org/packages/70/d5/1bab8e389c2261dba1764e9e793ed6830a63f830fdbec581a242c7c46bda/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", size = 442717 }, + { url = "https://files.pythonhosted.org/packages/82/a1/a45f3e30835b553379b3a56ea6c4eb622cf11e72008229af840e4596a8ea/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", size = 385721 }, + { url = "https://files.pythonhosted.org/packages/a6/27/780c942de3120bdd4d0e69583f9c96e179dfff082f6ecbb46b8d6488841f/rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", size = 415824 }, + { url = "https://files.pythonhosted.org/packages/94/0b/aa0542ca88ad20ea719b06520f925bae348ea5c1fdf201b7e7202d20871d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", size = 561227 }, + { url = "https://files.pythonhosted.org/packages/0d/92/3ed77d215f82c8f844d7f98929d56cc321bb0bcfaf8f166559b8ec56e5f1/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", size = 587424 }, + { url = "https://files.pythonhosted.org/packages/09/42/cacaeb047a22cab6241f107644f230e2935d4efecf6488859a7dd82fc47d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", size = 555953 }, + { url = "https://files.pythonhosted.org/packages/e6/52/c921dc6d5f5d45b212a456c1f5b17df1a471127e8037eb0972379e39dff4/rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", size = 221339 }, + { url = "https://files.pythonhosted.org/packages/f2/c7/f82b5be1e8456600395366f86104d1bd8d0faed3802ad511ef6d60c30d98/rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", size = 235786 }, + { url = "https://files.pythonhosted.org/packages/d0/bf/36d5cc1f2c609ae6e8bf0fc35949355ca9d8790eceb66e6385680c951e60/rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", size = 351657 }, + { url = "https://files.pythonhosted.org/packages/24/2a/f1e0fa124e300c26ea9382e59b2d582cba71cedd340f32d1447f4f29fa4e/rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", size = 341829 }, + { url = "https://files.pythonhosted.org/packages/cf/c2/0da1231dd16953845bed60d1a586fcd6b15ceaeb965f4d35cdc71f70f606/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", size = 384220 }, + { url = "https://files.pythonhosted.org/packages/c7/73/a4407f4e3a00a9d4b68c532bf2d873d6b562854a8eaff8faa6133b3588ec/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", size = 391009 }, + { url = "https://files.pythonhosted.org/packages/a9/c3/04b7353477ab360fe2563f5f0b176d2105982f97cd9ae80a9c5a18f1ae0f/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", size = 426989 }, + { url = "https://files.pythonhosted.org/packages/8d/e6/e4b85b722bcf11398e17d59c0f6049d19cd606d35363221951e6d625fcb0/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", size = 441544 }, + { url = "https://files.pythonhosted.org/packages/27/fc/403e65e56f65fff25f2973216974976d3f0a5c3f30e53758589b6dc9b79b/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", size = 385179 }, + { url = "https://files.pythonhosted.org/packages/57/9b/2be9ff9700d664d51fd96b33d6595791c496d2778cb0b2a634f048437a55/rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", size = 415103 }, + { url = "https://files.pythonhosted.org/packages/bb/a5/03c2ad8ca10994fcf22dd2150dd1d653bc974fa82d9a590494c84c10c641/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", size = 560916 }, + { url = "https://files.pythonhosted.org/packages/ba/2e/be4fdfc8b5b576e588782b56978c5b702c5a2307024120d8aeec1ab818f0/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", size = 587062 }, + { url = "https://files.pythonhosted.org/packages/67/e0/2034c221937709bf9c542603d25ad43a68b4b0a9a0c0b06a742f2756eb66/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", size = 555734 }, + { url = "https://files.pythonhosted.org/packages/ea/ce/240bae07b5401a22482b58e18cfbabaa392409b2797da60223cca10d7367/rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", size = 220663 }, + { url = "https://files.pythonhosted.org/packages/cb/f0/d330d08f51126330467edae2fa4efa5cec8923c87551a79299380fdea30d/rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", size = 235503 }, + { url = "https://files.pythonhosted.org/packages/f7/c4/dbe1cc03df013bf2feb5ad00615038050e7859f381e96fb5b7b4572cd814/rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", size = 347698 }, + { url = "https://files.pythonhosted.org/packages/a4/3a/684f66dd6b0f37499cad24cd1c0e523541fd768576fa5ce2d0a8799c3cba/rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", size = 337330 }, + { url = "https://files.pythonhosted.org/packages/82/eb/e022c08c2ce2e8f7683baa313476492c0e2c1ca97227fe8a75d9f0181e95/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", size = 380022 }, + { url = "https://files.pythonhosted.org/packages/e4/21/5a80e653e4c86aeb28eb4fea4add1f72e1787a3299687a9187105c3ee966/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", size = 390754 }, + { url = "https://files.pythonhosted.org/packages/37/a4/d320a04ae90f72d080b3d74597074e62be0a8ecad7d7321312dfe2dc5a6a/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", size = 423840 }, + { url = "https://files.pythonhosted.org/packages/87/70/674dc47d93db30a6624279284e5631be4c3a12a0340e8e4f349153546728/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", size = 438970 }, + { url = "https://files.pythonhosted.org/packages/3f/64/9500f4d66601d55cadd21e90784cfd5d5f4560e129d72e4339823129171c/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", size = 383146 }, + { url = "https://files.pythonhosted.org/packages/4d/45/630327addb1d17173adcf4af01336fd0ee030c04798027dfcb50106001e0/rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", size = 408294 }, + { url = "https://files.pythonhosted.org/packages/5f/ef/8efb3373cee54ea9d9980b772e5690a0c9e9214045a4e7fa35046e399fee/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", size = 556345 }, + { url = "https://files.pythonhosted.org/packages/54/01/151d3b9ef4925fc8f15bfb131086c12ec3c3d6dd4a4f7589c335bf8e85ba/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", size = 582292 }, + { url = "https://files.pythonhosted.org/packages/30/89/35fc7a6cdf3477d441c7aca5e9bbf5a14e0f25152aed7f63f4e0b141045d/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", size = 553855 }, + { url = "https://files.pythonhosted.org/packages/8f/e0/830c02b2457c4bd20a8c5bb394d31d81f57fbefce2dbdd2e31feff4f7003/rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", size = 219100 }, + { url = "https://files.pythonhosted.org/packages/f8/30/7ac943f69855c2db77407ae363484b915d861702dbba1aa82d68d57f42be/rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", size = 233794 }, + { url = "https://files.pythonhosted.org/packages/8b/63/e29f8ee14fcf383574f73b6bbdcbec0fbc2e5fc36b4de44d1ac389b1de62/rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d", size = 360786 }, + { url = "https://files.pythonhosted.org/packages/d3/e0/771ee28b02a24e81c8c0e645796a371350a2bb6672753144f36ae2d2afc9/rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd", size = 350589 }, + { url = "https://files.pythonhosted.org/packages/cf/49/abad4c4a1e6f3adf04785a99c247bfabe55ed868133e2d1881200aa5d381/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493", size = 381848 }, + { url = "https://files.pythonhosted.org/packages/3a/7d/f4bc6d6fbe6af7a0d2b5f2ee77079efef7c8528712745659ec0026888998/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96", size = 387879 }, + { url = "https://files.pythonhosted.org/packages/13/b0/575c797377fdcd26cedbb00a3324232e4cb2c5d121f6e4b0dbf8468b12ef/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123", size = 423916 }, + { url = "https://files.pythonhosted.org/packages/54/78/87157fa39d58f32a68d3326f8a81ad8fb99f49fe2aa7ad9a1b7d544f9478/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad", size = 448410 }, + { url = "https://files.pythonhosted.org/packages/59/69/860f89996065a88be1b6ff2d60e96a02b920a262d8aadab99e7903986597/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9", size = 382841 }, + { url = "https://files.pythonhosted.org/packages/bd/d7/bc144e10d27e3cb350f98df2492a319edd3caaf52ddfe1293f37a9afbfd7/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e", size = 409662 }, + { url = "https://files.pythonhosted.org/packages/14/2a/6bed0b05233c291a94c7e89bc76ffa1c619d4e1979fbfe5d96024020c1fb/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338", size = 558221 }, + { url = "https://files.pythonhosted.org/packages/11/23/cd8f566de444a137bc1ee5795e47069a947e60810ba4152886fe5308e1b7/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566", size = 583780 }, + { url = "https://files.pythonhosted.org/packages/8d/63/79c3602afd14d501f751e615a74a59040328da5ef29ed5754ae80d236b84/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe", size = 553619 }, + { url = "https://files.pythonhosted.org/packages/9f/2e/c5c1689e80298d4e94c75b70faada4c25445739d91b94c211244a3ed7ed1/rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", size = 233338 }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -859,6 +1925,46 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, +] + [[package]] name = "tomli" version = "2.2.1" @@ -898,6 +2004,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, ] +[[package]] +name = "tornado" +version = "6.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, + { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, + { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, + { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, + { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, + { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, + { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, + { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, + { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, + { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, +] + [[package]] name = "tox" version = "4.24.1" @@ -920,6 +2044,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/04/b0d1c1b44c98583cab9eabb4acdba964fdf6b6c597c53cfb8870fd08cbbf/tox-4.24.1-py3-none-any.whl", hash = "sha256:57ba7df7d199002c6df8c2db9e6484f3de6ca8f42013c083ea2d4d1e5c6bdc75", size = 171829 }, ] +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "types-python-dateutil" +version = "2.9.0.20241206" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -929,6 +2071,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, +] + [[package]] name = "urllib3" version = "2.3.0" @@ -952,6 +2103,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, ] +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + +[[package]] +name = "webcolors" +version = "24.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, +] + +[[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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, +] + [[package]] name = "websocket-client" version = "1.8.0" From e6badcd45f8cec7f16e9c991de5a74555452a87b Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Wed, 19 Feb 2025 14:59:57 -0800 Subject: [PATCH 04/10] Keep porting over the tests --- firefly_client/fc_utils.py | 6 +- firefly_client/firefly_client.py | 34 ++- pyproject.toml | 2 +- test/cb.ipynb | 110 ------- test/container.py | 19 ++ test/firefly_slate_demo.py | 218 ++++++++++++++ test/test-simple-callback-in-lab.ipynb | 152 ---------- test/test_5_instances_3_channels.py | 36 +-- test/test_basic_demo_tableload.py | 45 +++ test/test_basic_tableload.py | 50 ---- test/test_demo_3color.py | 85 ++++++ test/test_demo_HiPS.py | 48 ++++ test/test_demo_advanced_all.py | 270 ++++++++++++++++++ test/test_demo_advanced_steps.py | 224 +++++++++++++++ test/test_demo_advanced_table_images.py | 54 ++++ ...test_demo_advanced_tables_images_upload.py | 59 ++++ test/test_demo_basic.py | 197 +++++++++++++ test/test_demo_lsst-footprint.py | 87 ++++++ test/test_demo_region.py | 97 +++++++ test/test_demo_show-image.py | 24 ++ test/test_plot_interface.py | 204 +++++++++++++ 21 files changed, 1674 insertions(+), 347 deletions(-) delete mode 100644 test/cb.ipynb create mode 100644 test/container.py create mode 100644 test/firefly_slate_demo.py delete mode 100644 test/test-simple-callback-in-lab.ipynb create mode 100644 test/test_basic_demo_tableload.py delete mode 100644 test/test_basic_tableload.py create mode 100644 test/test_demo_3color.py create mode 100644 test/test_demo_HiPS.py create mode 100644 test/test_demo_advanced_all.py create mode 100644 test/test_demo_advanced_steps.py create mode 100644 test/test_demo_advanced_table_images.py create mode 100644 test/test_demo_advanced_tables_images_upload.py create mode 100644 test/test_demo_basic.py create mode 100644 test/test_demo_lsst-footprint.py create mode 100644 test/test_demo_region.py create mode 100644 test/test_demo_show-image.py create mode 100644 test/test_plot_interface.py diff --git a/firefly_client/fc_utils.py b/firefly_client/fc_utils.py index 554567c..3aa3fe2 100644 --- a/firefly_client/fc_utils.py +++ b/firefly_client/fc_utils.py @@ -2,6 +2,8 @@ import json import mimetypes import urllib.parse +from collections.abc import Iterable +from typing import Sequence class DebugMarker: @@ -80,9 +82,9 @@ def is_url(url): return image_source -def ensure3(val, name): +def ensure3[T](val: Sequence[T] | T, name: str) -> list[T]: """Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError""" - ret = val if isinstance(val, list) else [val, val, val] + ret: list[T] = list(val) if isinstance(val, Sequence) else [val, val, val] if not len(ret) == 3: raise ValueError("%s list should have 3 items" % name) return ret diff --git a/firefly_client/firefly_client.py b/firefly_client/firefly_client.py index ec891fe..c86fe48 100644 --- a/firefly_client/firefly_client.py +++ b/firefly_client/firefly_client.py @@ -12,6 +12,7 @@ import weakref import webbrowser from copy import copy +from typing import Literal, Optional from urllib.parse import urljoin import requests @@ -1831,11 +1832,24 @@ def set_stretch( def set_stretch_hprgb( self, - plot_id, - asinh_q_value=None, - scaling_k=1.0, - pedestal_value=1, - pedestal_type="percent", + plot_id: str | list[str], + asinh_q_value: Optional[float] = None, + scaling_k: float | list[float] = 1.0, + pedestal_value: float | list[float] = 1, + pedestal_type: ( + Literal["percent"] + | Literal["minmax"] + | Literal["absolute"] + | Literal["zscale"] + | Literal["sigma"] + | list[ + Literal["percent"] + | Literal["minmax"] + | Literal["absolute"] + | Literal["zscale"] + | Literal["sigma"] + ] + ) = "percent", ): """ Change the stretch of RGB image (hue-preserving rgb case). When a parameter is a list, @@ -1889,9 +1903,15 @@ def set_stretch_hprgb( return_val["rv_lst"] = [d["rv"] for d in st_data] return return_val - def set_color(self, plot_id, colormap_id=0, bias=0.5, contrast=1): + def set_color( + self, + plot_id: str | list[str], + colormap_id: int = 0, + bias: float = 0.5, + contrast: float = 1, + ): """ - Change the color attributes (color map, bias, constrast) of an image plot. + Change the color attributes (color map, bias, contrast) of an image plot. Parameters ---------- diff --git a/pyproject.toml b/pyproject.toml index 474732d..e740260 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ test = [ testpaths = ["firefly_client", "docs", "test"] doctest_plus = "enabled" text_file_format = "rst" -addopts = "--doctest-rst -n 4" +addopts = ["--doctest-rst", "-n 4", "--import-mode=importlib"] [tool.coverage.run] omit = [ diff --git a/test/cb.ipynb b/test/cb.ipynb deleted file mode 100644 index a4d5601..0000000 --- a/test/cb.ipynb +++ /dev/null @@ -1,110 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import firefly_client\n", - "from firefly_client import FireflyClient\n", - "\n", - "firefly_client.__dict__[\"__version__\"]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "FireflyClient._debug = False\n", - "fc = FireflyClient.make_lab_client()\n", - "fc.get_firefly_url()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_fits(\n", - " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Extensions can be made but there is not web socket connections until a listener is added\n", - "\n", - "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\")\n", - "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", - "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# A Web socket should not be made until this cell is added\n", - "\n", - "\n", - "def listener1(ev):\n", - " if False:\n", - " print(ev)\n", - " if \"data\" not in ev:\n", - " print(\"no data found in ev\")\n", - " return\n", - " data = ev[\"data\"]\n", - " if \"payload\" in data:\n", - " print(data[\"payload\"])\n", - " if \"type\" in data:\n", - " print(data[\"type\"])\n", - " if data[\"type\"] == \"POINT\":\n", - " print(\" image point: %s\" % data[\"ipt\"])\n", - " print(\" world point: %s\" % data[\"wpt\"])\n", - " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", - " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", - " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", - "\n", - "\n", - "fc.add_listener(listener1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/test/container.py b/test/container.py new file mode 100644 index 0000000..b0d5ca9 --- /dev/null +++ b/test/container.py @@ -0,0 +1,19 @@ +import random + +from pytest_container.container import Container, EntrypointSelection +from pytest_container.inspect import NetworkProtocol, PortForwarding + +FIREFLY_CONTAINER = Container( + url="docker.io/ipac/firefly:latest", + extra_launch_args=["--memory=4g"], + entry_point=EntrypointSelection.AUTO, + singleton=True, + forwarded_ports=[ + PortForwarding( + container_port=8080, + protocol=NetworkProtocol.TCP, + host_port=random.randint(8000, 65534), + bind_ip="127.0.0.1", + ) + ], +) diff --git a/test/firefly_slate_demo.py b/test/firefly_slate_demo.py new file mode 100644 index 0000000..0816983 --- /dev/null +++ b/test/firefly_slate_demo.py @@ -0,0 +1,218 @@ +import astropy.utils.data + + +def download_from(url): + return astropy.utils.data.download_file(url, timeout=120, cache=True) + + +def load_moving_table(row, col, width, height, fc, cell_id=None): + moving_tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl" + ) + + r = fc.add_cell(row, col, width, height, "tables", cell_id) + meta_info = { + "datasetInfoConverterId": "SimpleMoving", + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "image_url", + } + tbl_name = fc.upload_file(moving_tbl_name) + + if r["success"]: + fc.show_table( + tbl_name, + tbl_id="movingtbl", + title="A moving object table", + page_size=15, + meta=meta_info, + ) + + +def add_simple_m31_image_table(fc): + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl" + ) + + meta_info = { + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "FITS", + } + fc.show_table( + fc.upload_file(tbl_name), + title="A table of m31 images", + page_size=15, + meta=meta_info, + ) + + +def add_simple_image_table(fc): + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table4.tbl" + ) + + meta_info = {"datasource": "FITS"} + fc.show_table( + fc.upload_file(tbl_name), + title="A table of simple images", + page_size=15, + meta=meta_info, + ) + + +def add_table_for_chart(fc): + tbl_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl" + ) + + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="tbl_chart", + title="table for xyplot and histogram", + page_size=15, + ) + + +def load_image(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "images", cell_id) + + image_name = download_from( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + + if r["success"]: + fc.show_fits( + file_on_server=fc.upload_file(image_name), + viewer_id=r["cell_id"], + title="WISE Cutout", + ) + + +def load_image_metadata(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "tableImageMeta", cell_id) + + if r["success"]: + fc.show_image_metadata(viewer_id=r["cell_id"]) + + +def load_coverage_image(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "coverageImage", cell_id) + + if r["success"]: + fc.show_coverage(viewer_id=r["cell_id"]) + + +def load_moving(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "images", cell_id) + + if r["success"]: + v_id = r["cell_id"] + fc.show_fits( + plot_id="m49025b_143_2", + viewer_id=v_id, + OverlayPosition="330.347003;-2.774482;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49025b143-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49273b_134_2", + viewer_id=v_id, + OverlayPosition="333.539702;-0.779310;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49273b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49277b_135_1", + viewer_id=v_id, + OverlayPosition="333.589054;-0.747251;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49277b135-w1", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits", + ) + fc.show_fits( + plot_id="m49289b_134_2", + viewer_id=v_id, + OverlayPosition="333.736578;-0.651222;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49289b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits", + ) + + +def load_xy(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) + + if r["success"]: + trace1 = { + "tbl_id": "tbl_chart", + "x": "tables::ra1", + "y": "tables::dec1", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Coordinates", + "xaxis": {"title": "ra1 (deg)"}, + "yaxis": {"title": "dec1 (deg)"}, + } + fc.show_chart(group_id=r["cell_id"], layout=layout_s, data=trace_data) + + +def load_histogram(row, col, width, height, fc, cell_id=None): + r = fc.add_cell(row, col, width, height, "xyPlots", cell_id) + + if r["success"]: + histData = [ + { + "type": "fireflyHistogram", + "name": "magzp", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "tbl_chart", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "magzp", + }, + }, + } + ] + + layout_hist = { + "title": "Magnitude Zeropoints", + "xaxis": {"title": "magzp"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(group_id=r["cell_id"], layout=layout_hist, data=histData) + + +def load_first_image_in_random(fc): + img_name = download_from( + "http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits" + ) + + fc.show_fits(file_on_server=fc.upload_file(img_name)) + + +def load_second_image_in_random(fc): + fc.show_fits( + plot_id="xxq", + Service="TWOMASS", + Title="2mass from service", + ZoomType="LEVEL", + initZoomLevel=2, + SurveyKey="asky", + SurveyKeyBand="k", + WorldPt="10.68479;41.26906;EQ_J2000", + SizeInDeg=".12", + ) diff --git a/test/test-simple-callback-in-lab.ipynb b/test/test-simple-callback-in-lab.ipynb deleted file mode 100644 index f0b7f84..0000000 --- a/test/test-simple-callback-in-lab.ipynb +++ /dev/null @@ -1,152 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import FireflyClient" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# This test can be modified to make multiple firefly_clients to prove there is only 1 per channel\n", - "\n", - "# some host\n", - "lsst_demo_host = \"https://lsst-demo.ncsa.illinois.edu/firefly\"\n", - "local_host = \"http://127.0.0.1:8080/firefly\"\n", - "irsa = \"https://irsa.ipac.caltech.edu/irsaviewer\"\n", - "fd = \"https://fireflydev.ipac.caltech.edu/firefly\"\n", - "\n", - "# host = 'http://127.0.0.1:8080/suit'\n", - "host = local_host\n", - "channel1 = \"channel-test-1\"\n", - "FireflyClient._debug = False\n", - "# fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True)\n", - "fc = FireflyClient.make_lab_client(\n", - " start_browser_tab=False, start_tab=True, verbose=True\n", - ")\n", - "# fc = FireflyClient.make_lab_client(start_tab=False)\n", - "fc.get_firefly_url()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.get_firefly_url()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.show_fits(\n", - " url=\"http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits\",\n", - " plot_id=\"x2\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "fc.set_stretch(\"x1\", stype=\"zscale\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Extensions can be made but there is not web socket connections until a listener is added\n", - "\n", - "fc.add_extension(ext_type=\"LINE_SELECT\", title=\"a line\", shortcut_key=\"meta-e\")\n", - "fc.add_extension(ext_type=\"AREA_SELECT\", title=\"a area\")\n", - "fc.add_extension(ext_type=\"POINT\", title=\"a point\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# A Web socket should not be made until this cell is added\n", - "\n", - "\n", - "def listener1(ev):\n", - " if False:\n", - " print(ev)\n", - " if \"data\" not in ev:\n", - " print(\"no data found in ev\")\n", - " return\n", - " data = ev[\"data\"]\n", - " if \"payload\" in data:\n", - " print(data[\"payload\"])\n", - " if \"type\" in data:\n", - " print(data[\"type\"])\n", - " if data[\"type\"] == \"POINT\":\n", - " print(\" image point: %s\" % data[\"ipt\"])\n", - " print(\" world point: %s\" % data[\"wpt\"])\n", - " if data[\"type\"] == \"LINE_SELECT\" or data[\"type\"] == \"AREA_SELECT\":\n", - " print(\" image points: %s to %s\" % (data[\"ipt0\"], data[\"ipt1\"]))\n", - " print(\" world points: %s to %s\" % (data[\"wpt0\"], data[\"wpt1\"]))\n", - "\n", - "\n", - "fc.add_listener(listener1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from firefly_client import __version__ as v\n", - "\n", - "v" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.13.1" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py index 7825723..186a776 100644 --- a/test/test_5_instances_3_channels.py +++ b/test/test_5_instances_3_channels.py @@ -1,31 +1,17 @@ import time -import random -from firefly_client import FireflyClient -from pytest_container.container import Container, ContainerData, EntrypointSelection -from pytest_container.inspect import NetworkProtocol, PortForwarding - -FIREFLY_CONTAINER = Container( - url="docker.io/ipac/firefly:latest", - extra_launch_args=["--memory=4g"], - entry_point=EntrypointSelection.AUTO, - forwarded_ports=[ - PortForwarding( - container_port=8080, - protocol=NetworkProtocol.TCP, - host_port=random.randint(8000,65534), - bind_ip="127.0.0.1", - ) - ], -) -CONTAINER_IMAGES = [FIREFLY_CONTAINER] +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER -def test_5_instances_3_channels(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port >= 8000 - assert auto_container.forwarded_ports[0].host_port <= 65534 - assert auto_container.forwarded_ports[0].container_port == 8080 - assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_5_instances_3_channels(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" time.sleep(5) @@ -45,7 +31,7 @@ def listener4(ev): print("l4") print(ev) - host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" channel1 = "channel-test-1" channel2 = "channel-test-2" channel3 = "channel-test-3" diff --git a/test/test_basic_demo_tableload.py b/test/test_basic_demo_tableload.py new file mode 100644 index 0000000..b86f541 --- /dev/null +++ b/test/test_basic_demo_tableload.py @@ -0,0 +1,45 @@ +import os +import time +from pathlib import Path +from urllib import request + +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_basic_tableload(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + test_data_path = Path(os.curdir) + + # This was originally /hydra/cm/MF.20210502.18830.fits + localfile = os.path.join(test_data_path, "wise-00.fits") + + request.urlretrieve( + "http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/demo/wise-00.fits", + localfile, + ) + + filename = fc.upload_file(localfile) + fc.show_table(filename) + fc.show_table(filename, table_index=2) + + # localfile = os.path.join(testdata_repo_path, "Mr31objsearch.xml") + # filename = fc.upload_file(localfile) + + # fc.show_table(filename, tbl_id="votable-0") + # fc.show_table(filename, tbl_id="votable-1", table_index=1) + + os.remove(localfile) diff --git a/test/test_basic_tableload.py b/test/test_basic_tableload.py deleted file mode 100644 index bea6b07..0000000 --- a/test/test_basic_tableload.py +++ /dev/null @@ -1,50 +0,0 @@ -import os -import random -import time - -from firefly_client import FireflyClient -from pytest_container.container import Container, ContainerData, EntrypointSelection -from pytest_container.inspect import NetworkProtocol, PortForwarding - -FIREFLY_CONTAINER = Container( - url="docker.io/ipac/firefly:latest", - extra_launch_args=["--memory=4g"], - entry_point=EntrypointSelection.AUTO, - forwarded_ports=[ - PortForwarding( - container_port=8080, - protocol=NetworkProtocol.TCP, - host_port=random.randint(8000, 65534), - bind_ip="127.0.0.1", - ) - ], -) - -CONTAINER_IMAGES = [FIREFLY_CONTAINER] - - -def test_tableload(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port >= 8000 - assert auto_container.forwarded_ports[0].host_port <= 65534 - assert auto_container.forwarded_ports[0].container_port == 8080 - assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" - - time.sleep(5) - host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" - fc = FireflyClient.make_client(host) - - testdata_repo_path = "/hydra/cm" - - localfile = os.path.join(testdata_repo_path, "MF.20210502.18830.fits") - filename = fc.upload_file(localfile) - - fc.show_table(filename) - - fc.show_table(filename, table_index=2) - - localfile = os.path.join(testdata_repo_path, "Mr31objsearch.xml") - filename = fc.upload_file(localfile) - - fc.show_table(filename, tbl_id="votable-0") - - fc.show_table(filename, tbl_id="votable-1", table_index=1) diff --git a/test/test_demo_3color.py b/test/test_demo_3color.py new file mode 100644 index 0000000..84214c8 --- /dev/null +++ b/test/test_demo_3color.py @@ -0,0 +1,85 @@ +import time + +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_3color(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + target = "210.80227;54.34895;EQ_J2000" # Galaxy M101 + viewer_id = "3C" + r = fc.add_cell(0, 0, 1, 1, "images", viewer_id) + rv = "92,-2,92,8,NaN,2,44,25,600,120" + if r["success"]: + threeC = [ + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "3a", + "SurveyKeyBand": "3", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": ".14", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "3a", + "SurveyKeyBand": "2", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": ".14", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "3a", + "SurveyKeyBand": "1", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": ".14", + }, + ] + + fc.show_fits_3color(threeC, plot_id="wise_m101", viewer_id=viewer_id) + + # In[3]: + + # Set stretch using hue-preserving algorithm with scaling coefficients .15 for RED, 1.0 for GREEN, and .4 for BLUE. + fc.set_stretch_hprgb( + plot_id="wise_m101", asinh_q_value=4.2, scaling_k=[0.15, 1, 0.4] + ) + + # In[4]: + + # Set per-band stretch + fc.set_stretch(plot_id="wise_m101", stype="ztype", algorithm="asinh", band="ALL") + + # In[5]: + + # Set contrast and bias for each band + fc.set_rgb_colors(plot_id="wise_m101", bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2]) + + # In[6]: + + # Set to use red and blue band only, with default bias and contrast + fc.set_rgb_colors(plot_id="wise_m101", use_green=False) + + +# In[ ]: diff --git a/test/test_demo_HiPS.py b/test/test_demo_HiPS.py new file mode 100644 index 0000000..67254bc --- /dev/null +++ b/test/test_demo_HiPS.py @@ -0,0 +1,48 @@ +import time + +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_hi_ps(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + target = "148.892;69.0654;EQ_J2000" + + viewer_id = "hipsDIV1" + r = fc.add_cell(0, 0, 4, 2, "images", viewer_id) + if r["success"]: + hips_url = "http://alasky.u-strasbg.fr/DSS/DSSColor" + status = fc.show_hips( + viewer_id=viewer_id, + plot_id="aHipsID1-1", + hips_root_url=hips_url, + Title="HiPS-DSS", + WorldPt=target, + ) + + fc.set_color("aHipsID1-1", colormap_id=6, bias=0.6, contrast=1.5) + + hips_url = "http://alasky.u-strasbg.fr/DSS/DSS2Merged" + status = fc.show_hips( + plot_id="aHipsID1-2", hips_root_url=hips_url, Title="HiPS-DSS2", WorldPt=target + ) + + assert status is not None + + hips_url = "http://alasky.u-strasbg.fr/AllWISE/RGB-W4-W2-W1" + status = fc.show_hips(plot_id="aHipsID2", hips_root_url=hips_url) + + assert status is not None diff --git a/test/test_demo_advanced_all.py b/test/test_demo_advanced_all.py new file mode 100644 index 0000000..1c6ec22 --- /dev/null +++ b/test/test_demo_advanced_all.py @@ -0,0 +1,270 @@ +import time + +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_adv(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov) + + target = "10.68479;41.26906;EQ_J2000" # Galaxy M31 + # other notable targets to try + # target = '148.88822;69.06529;EQ_J2000' #Galaxy M81 + # target = '202.48417;47.23056;EQ_J2000' #Galaxy M51 + # target = '136.9316774;+1.1195886;galactic' # W5 star-forming region + + r = fc.add_cell(0, 4, 2, 2, "tables", "main") + + if r["success"]: + fc.show_table( + tbl_id="wiseCatTbl", + title="WISE catalog", + target_search_info={ + "catalogProject": "WISE", + "catalog": "allwise_p3as_psd", + "position": target, + "SearchMethod": "Cone", + "radius": 1200, + }, + options={"removable": True, "showUnits": False, "showFilters": True}, + ) + + # Add first chart - scatter (plot.ly direct plot) + # in cell 0, 0, 2, 2, + viewer_id = "newChartContainer" + r = fc.add_cell(0, 0, 2, 2, "xyPlots", viewer_id) + + if r["success"]: + trace1 = { + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro-w2mpro", + "y": "tables::w2mpro-w3mpro", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Color-Color", + "xaxis": {"title": "w1mpro-w2mpro (mag)"}, + "yaxis": {"title": "w2mpro-w3mpro (mag)"}, + } + fc.show_chart(group_id=viewer_id, layout=layout_s, data=trace_data) + + # Add second chart - heatmap (plot.ly direct plot) + # in cell 2, 0, 2, 3 + viewer_id = "heatMapContainer" + r = fc.add_cell(2, 0, 2, 3, "xyPlots", viewer_id) + print(r) + + if r["success"]: + dataHM = [ + { + "type": "fireflyHeatmap", + "name": "w1 vs. w2", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w2mpro", + "colorscale": "Blues", + }, + { + "type": "fireflyHeatmap", + "name": "w1 vs. w3", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w3mpro", + "colorscale": "Reds", + "reversescale": True, + }, + { + "type": "fireflyHeatmap", + "name": "w1 vs. w4", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w4mpro", + "colorscale": "Greens", + }, + ] + + layout_hm = { + "title": "Magnitude-magnitude densities", + "xaxis": {"title": "w1 photometry (mag)"}, + "yaxis": {"title": ""}, + "firefly": {"xaxis": {"min": 5, "max": 20}, "yaxis": {"min": 4, "max": 18}}, + } + + fc.show_chart(group_id=viewer_id, layout=layout_hm, data=dataHM) + + # Add third chart - histogram (plot.ly direct plot) + # in cell 2, 2, 2, 3 + viewer_id = "histContainer" + r = fc.add_cell(2, 2, 2, 3, "xyPlots", viewer_id) + + if r["success"]: + dataH = [ + { + "type": "fireflyHistogram", + "name": "w1mpro", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "wiseCatTbl", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "w1mpro", + }, + }, + }, + { + "type": "fireflyHistogram", + "name": "w2mpro", + "marker": {"color": "rgba(102, 153, 0, 0.7)"}, + "firefly": { + "tbl_id": "wiseCatTbl", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 40, + "columnOrExpr": "w2mpro", + }, + }, + }, + ] + + layout_hist = { + "title": "Photometry histogram", + "xaxis": {"title": "photometry (mag)"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(group_id=viewer_id, layout=layout_hist, data=dataH) + + assert result is not None + + # Add fourth chart - 3D plot (plot.ly direct plot) + # in cell 2, 4, 2, 3, + viewer_id = "3dChartContainer" + r = fc.add_cell(2, 4, 2, 3, "xyPlots", viewer_id) + + if r["success"]: + data3d = [ + { + "type": "scatter3d", + "name": "w1-w2-w3", + "tbl_id": "wiseCatTbl", + "x": "tables::w1mpro", + "y": "tables::w2mpro", + "z": "tables::w3mpro", + "mode": "markers", + "marker": { + "size": 3, + "color": "rgba(127, 127, 127, 1)", + "line": {"color": "rgba(127, 127, 127, 0.5)", "width": 1}, + }, + "hoverinfo": "x+y+z", + } + ] + + fsize = {"size": 11} + layout3d = { + "title": "Photometry in band 1, 2, 3", + "scene": { + "xaxis": {"title": "w1 (mag)", "titlefont": fsize}, + "yaxis": {"title": "w2 (mag)", "titlefont": fsize}, + "zaxis": {"title": "w3 (mag)", "titlefont": fsize}, + }, + } + + fc.show_chart(group_id=viewer_id, is_3d=True, layout=layout3d, data=data3d) + + # Add a three color fits + # in cell 0, 2, 2, 2 + viewer_id = "3C" + r = fc.add_cell(0, 2, 2, 2, "images", viewer_id) + rv = "92,-2,92,8,NaN,2,44,25,600,120" + if r["success"]: + threeC = [ + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "1", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "2", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "3", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + ] + fc.show_fits_3color(threeC, viewer_id=viewer_id) + + fc.change_triview_layout(FireflyClient.BIVIEW_IChCov_T) + + rv = "92,-2,92,8,NaN,2,44,25,600,120" + threeC = [ + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "1", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "2", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + { + "Type": "SERVICE", + "Service": "WISE", + "Title": "3 color", + "SurveyKey": "Atlas", + "SurveyKeyBand": "3", + "WorldPt": target, + "RangeValues": rv, + "SizeInDeg": "1", + }, + ] + fc.show_fits_3color(threeC) diff --git a/test/test_demo_advanced_steps.py b/test/test_demo_advanced_steps.py new file mode 100644 index 0000000..db8b67c --- /dev/null +++ b/test/test_demo_advanced_steps.py @@ -0,0 +1,224 @@ +import time + +import pytest +from astropy.utils.data import download_file +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_adv_steps(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + fc.change_triview_layout(FireflyClient.BIVIEW_T_IChCov) + + # ## Display tables, images, XY charts, and Histograms in Window/Grid like layout + # + # Each rendered unit on Firefly Slate Viewer is called a'cell'. To render a cell and its content, the cell location (row, column, width, height), element type and cell ID are needed. (row, column) and (width, height) represent the position and size of the cell in terms of the grid blocks on Firefly Slate Viewer. Element types include types of 'tables', images', 'xyPlots', 'tableImageMeta' and 'coverageImage'. + + # We use astropy here for convenience, but firefly_client itself does not depend on astropy. + + # + # ## Display tables and catalogs + # + # + # Add some tables into cell 'main' (default cell id for tables) + # + # + # - add first table in cell 'main': + # - 'main' is the cell id currently supported by Firefly for element type 'tables' + # - this cell is shown at row = 0, col = 0 with width = 4, height = 2 + + tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/moving/MOST_results-sample.tbl", + timeout=120, + cache=True, + ) + meta_info = { + "datasetInfoConverterId": "SimpleMoving", + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "image_url", + } + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="movingtbl", + title="A moving object table", + page_size=15, + meta=meta_info, + ) + + # - add 2nd table in cell 'main' for chart and histogram + tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl", + timeout=120, + cache=True, + ) + fc.show_table( + fc.upload_file(tbl_name), + tbl_id="tbl_chart", + title="table for xyplot and histogram", + page_size=15, + ) + + # - add 3rd table in cell 'main' + tbl_name = download_file( + "http://web.ipac.caltech.edu.s3-us-west-2.amazonaws.com/staff/roby/data-products-test/dp-test.tbl", + timeout=120, + cache=True, + ) + meta_info = {"datasource": "DP"} + fc.show_table( + fc.upload_file(tbl_name), + title="A table of simple images", + page_size=15, + meta=meta_info, + ) + + # - add 4th table in cell 'main' + tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/test-table-m31.tbl", + timeout=120, + cache=True, + ) + + meta_info = { + "positionCoordColumns": "ra_obj;dec_obj;EQ_J2000", + "datasource": "FITS", + } + fc.show_table( + fc.upload_file(tbl_name), + title="A table of m31 images", + page_size=15, + meta=meta_info, + ) + + # ## Add different types of image displays + # + # - show cell with id 'image-meta' containaing image from the active table containing datasource column in cell 'main' + # - the cell is shown at row = 2, col = 0 with width = 4, height = 2 + # - show cell 'wise-content' containing fits at row = 0, col = 4 with width = 2, height = 2 + image_name = download_file( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + fc.show_fits(file_on_server=fc.upload_file(image_name), title="WISE Cutout") + + # - show 4 fits of moving objects in cell 'movingStff' at row = 2, col = 4 with width = 2, height = 2 + fc.show_fits( + plot_id="m49025b_143_2", + OverlayPosition="330.347003;-2.774482;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49025b143-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49025b143-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49273b_134_2", + OverlayPosition="333.539702;-0.779310;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49273b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49273b134-w2-int-1b.fits", + ) + fc.show_fits( + plot_id="m49277b_135_1", + OverlayPosition="333.589054;-0.747251;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49277b135-w1", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49277b135-w1-int-1b.fits", + ) + fc.show_fits( + plot_id="m49289b_134_2", + OverlayPosition="333.736578;-0.651222;EQ_J2000", + ZoomType="TO_WIDTH_HEIGHT", + Title="49289b134-w2", + plotGroupId="movingGroup", + URL="http://web.ipac.caltech.edu/staff/roby/demo/moving/49289b134-w2-int-1b.fits", + ) + + # ## Add charts (xy plot and histogram) + # + # - show the cell 'chart-cell-xy' with xy plot related to the table with id 'tbl_chart' in cell 'main' + # - this cell is shown at row = 4, col = 0 with width = 2, height = 3 + trace1 = { + "tbl_id": "tbl_chart", + "x": "tables::ra1", + "y": "tables::dec1", + "mode": "markers", + "type": "scatter", + "marker": {"size": 4}, + } + trace_data = [trace1] + + layout_s = { + "title": "Coordinates", + "xaxis": {"title": "ra1 (deg)"}, + "yaxis": {"title": "dec1 (deg)"}, + } + fc.show_chart(layout=layout_s, data=trace_data) + + # - show the cell with histogram related to the table with id 'tbl_chart' in cell 'main', + # - this cell is shown at row = 4, col = 2, with width = 2, height = 3 + # - the cell id is automatically created by FireflyClient in case it is not specified externally. + histData = [ + { + "type": "fireflyHistogram", + "name": "magzp", + "marker": {"color": "rgba(153, 51, 153, 0.8)"}, + "firefly": { + "tbl_id": "tbl_chart", + "options": { + "algorithm": "fixedSizeBins", + "fixedBinSizeSelection": "numBins", + "numBins": 30, + "columnOrExpr": "magzp", + }, + }, + } + ] + + layout_hist = { + "title": "Magnitude Zeropoints", + "xaxis": {"title": "magzp"}, + "yaxis": {"title": ""}, + } + result = fc.show_chart(layout=layout_hist, data=histData) + assert result is not None + + # ## Add more images + # + # - show coverage image associated with the active table in cell 'main' + # - this cell is shown at row = 4, col = 4 with width = 2, height = 3 + # - show image in random location without passing cell location and cell id (i.e. without calling add_cell) + # - the image is shown in the cell with id 'DEFAULT_FITS_VIEWER_ID' in default, + # - and the cell is automatically located by Firefly + img_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits", + timeout=120, + cache=True, + ) + fc.show_fits(file_on_server=fc.upload_file(img_name)) + fc.show_fits(plot_id="zzz", file_on_server=fc.upload_file(img_name)) + + # - show second image in random. it is shown in the same cell as the previous one + fc.show_fits( + plot_id="xxq", + Service="TWOMASS", + Title="2mass from service", + ZoomType="LEVEL", + initZoomLevel=2, + SurveyKey="asky", + SurveyKeyBand="k", + WorldPt="10.68479;41.26906;EQ_J2000", + SizeInDeg=".12", + ) diff --git a/test/test_demo_advanced_table_images.py b/test/test_demo_advanced_table_images.py new file mode 100644 index 0000000..491505f --- /dev/null +++ b/test/test_demo_advanced_table_images.py @@ -0,0 +1,54 @@ +import time + +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_adv_table_imgs(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + # ## Display tables and catalogs + + # Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + # Add some tables into cell 'main' (default grid viewer id for tables) + # first table in cell 'main' + fs.load_moving_table(0, 0, 4, 2, fc) + + # add table in cell 'main' for chart and histogram + fs.add_table_for_chart(fc) + # add table in cell 'main' + fs.add_simple_image_table(fc) + # add table in cell 'main' + fs.add_simple_m31_image_table(fc) + # ## Add different types of image displays + # show cell containing the image from the active table with datasource column in cell 'main' + fs.load_image_metadata(2, 0, 4, 2, fc, "image-meta") + # show cell containing FITS in cell 'wise-cutout' + fs.load_image(0, 4, 2, 2, fc, "wise-cutout") # load an image + # show cell with 4 FITS of moving objects in cell 'movingStff' + fs.load_moving(2, 4, 2, 2, fc, "movingStuff") + # ## Add charts (xy plot and histogram) + # show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main' + fs.load_xy(4, 0, 2, 3, fc, "chart-cell-xy") + # show histogram associated with the table for chart in cell 'main', the cell id is generated by firefly_client + fs.load_histogram(4, 2, 2, 3, fc) + # ## Add more images + # show cell containing coverage image associated with the active table in cell 'main' + fs.load_coverage_image(4, 4, 3, 3, fc, "image-coverage") + # show cell containing image in ranmon location without passing the location and cell id + fs.load_first_image_in_random(fc) + # show second image in random location. This image is located in the same cell as the previous one + fs.load_second_image_in_random(fc) diff --git a/test/test_demo_advanced_tables_images_upload.py b/test/test_demo_advanced_tables_images_upload.py new file mode 100644 index 0000000..34d782b --- /dev/null +++ b/test/test_demo_advanced_tables_images_upload.py @@ -0,0 +1,59 @@ +import time + +import pytest +from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +def test_adv_table_imgs_upload(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + # ## Display tables, images, XY charts, and Histograms in Window/Grid like layout + # + # Each rendered unit on Firefly. + # Please refer to firefly_slate_demo.py which contains functions to render cells with element tables, images, xy charts or histograms onto Firefly Slate Viewer by using firefly_client API. + # ## Display tables and catalogs + # Open a browser to the firefly server in a new tab. Only works when running the notebook locally. + # Add some tables into cell 'main' (default grid viewer id for tables) + # first table in cell 'main' + fs.load_moving_table(0, 0, 4, 2, fc) + # add table in cell 'main' for chart and histogram + fs.add_table_for_chart(fc) + # add table in cell 'main' + fs.add_simple_image_table(fc) + # add table in cell 'main' + fs.add_simple_m31_image_table(fc) + f = "/Users/roby/fits/2mass-m31-2412rows.tbl" + file = fc.upload_file(f) + fc.show_table(file) + fc.show_table("${cache-dir}/upload_2482826742890803252.fits") + # ## Add different types of image displays + # show cell containing the image from the active table with datasource column in cell 'main' + fs.load_image_metadata(2, 0, 4, 2, fc, "image-meta") + # show cell containing FITS in cell 'wise-cutout' + fs.load_image(0, 4, 2, 2, fc, "wise-cutout") # load an image + # show cell with 4 FITS of moving objects in cell 'movingStff' + fs.load_moving(2, 4, 2, 2, fc, "movingStuff") + # ## Add charts (xy plot and histogram) + # show xy plot in cell 'chart-cell-xy' associated with the table for chart in cell 'main' + fs.load_xy(4, 0, 2, 3, fc, "chart-cell-xy") + # show histogram associated with the table for chart in cell 'main', the cell id is generated by firefly_client + fs.load_histogram(4, 2, 2, 3, fc) + # ## Add more images + # show cell containing coverage image associated with the active table in cell 'main' + fs.load_coverage_image(4, 4, 3, 3, fc, "image-coverage") + # show cell containing image in ranmon location without passing the location and cell id + fs.load_first_image_in_random(fc) + # show second image in random location. This image is located in the same cell as the previous one + fs.load_second_image_in_random(fc) diff --git a/test/test_demo_basic.py b/test/test_demo_basic.py new file mode 100644 index 0000000..6a1830a --- /dev/null +++ b/test/test_demo_basic.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python +# coding: utf-8 + +# # Intro + +# This notebook demonstrates basic usage of the firefly_client API. +# +# Note that it may be necessary to wait for some cells (like those displaying an image) to complete before executing later cells. + +# ## Setup + +# Imports for firefly_client: + +# In[ ]: + + +import astropy.utils.data +from firefly_client import FireflyClient + +using_lab = False +# url = 'https://irsa.ipac.caltech.edu/irsaviewer' +url = "http://127.0.0.1:8080/firefly" +# FireflyClient._debug= True + + +# The firefly_client needs to connect to a firefly server. In this example, we use the IRSA Viewer application for the server: + +# Instantiate `FireflyClient` using the URL above: + +# In[ ]: + + +fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) + + +# ## Download Sample Data + +# For the following example, we download a sample image and table by using astropy (it's used only for convenience, firefly_client itself does not depend on astropy): + +# Download a cutout of a WISE band as 1 FITS image: + +# In[ ]: + + +image_url = ( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" +) +filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120) + + +# Download a 2MASS catalog using an IRSA VO Table Access Protocol ([TAP](https://irsa.ipac.caltech.edu/docs/program_interface/astropy_TAP.html)) search: + +# In[ ]: + + +table_url = ( + "http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&" + + "QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec)," + + "CIRCLE('J2000',70.0,20.0,0.1))=1" +) +tablename = astropy.utils.data.download_file(table_url, timeout=120, cache=True) + + +# ## Display Tables and Catalogs + +# Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + +# In[ ]: + + +# localbrowser, browser_url = fc.launch_browser() + + +# If it does not open automatically, try using the following command to display a web browser link to click on: + +# In[ ]: + + +fc.display_url() + + +# Alternatively, uncomment the lines below (while commenting out the 2 lines above) to display the browser application in the notebook: + +# In[ ]: + + +# from IPython.display import IFrame +# print('url: %s' % fc.get_firefly_url()) +# IFrame(fc.get_firefly_url(), 1100, 1000) + + +# The current example involves 2 images in FITS format to be viewed through the chosen browser, both in different ways. +# For the 1st method, upload a local file to the server and then display it: + +# In[ ]: + + +imval = fc.upload_file(filename) +status = fc.show_fits(file_on_server=imval, plot_id="wise-cutout", title="WISE Cutout") + + +# For the 2nd method, pull an image directly from a URL: + +# In[ ]: + + +status = fc.show_fits( + file_on_server=None, + plot_id="wise-fullimage", + URL="http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits", +) + + +# Upload a catalog table (with coordinates) to the server so it will overlay the image with markers by default. Note that markers can be visible on both "wise-cutout" and "wise-fullimage" since they would include the same field of view: + +# In[ ]: + + +file = fc.upload_file(tablename) +status = fc.show_table(file, tbl_id="tablemass", title="My 2MASS Catalog", page_size=50) + + +# Add an xy plot using the uploaded table data: + +# In[ ]: + + +status = fc.show_xyplot(tbl_id="tablemass", xCol="j_m", yCol="h_m-k_m") + + +# Alternatively, more generic method show_chart() can be used to create the same plot + +# In[ ]: + + +# trace0 = {'tbl_id': 'tablemass', 'x': "tables::j_m", 'y': "tables::h_m-k_m", +# 'type' : 'scatter', 'mode': 'markers'} +# status = fc.show_chart(data=[trace0]) + + +# Zoom into the full image by a factor of 2 (note the `plot_id` parameter we used earlier) + +# In[ ]: + + +status = fc.set_zoom("wise-fullimage", 2) + + +# Pan the full image to center on a given celestial coordinate: + +# In[ ]: + + +status = fc.set_pan("wise-fullimage", x=70, y=20, coord="J2000") + + +# Set the stretch for the full image based on IRAF zscale interval with a linear algorithm: + +# In[ ]: + + +status = fc.set_stretch("wise-fullimage", stype="zscale", algorithm="linear") + + +# Change color of the image: + +# In[ ]: + + +status = fc.set_color("wise-fullimage", colormap_id=6, bias=0.6, contrast=1.5) + + +# Add region data to the cutout image (2 areas to begin with): + +# In[ ]: + + +my_regions = [ + "image;polygon 125 25 160 195 150 150 #color=cyan", + "icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}", +] +status = fc.add_region_data( + region_data=my_regions, region_layer_id="layer1", plot_id="wise-cutout" +) + + +# Remove the second region whlie keeping the first one visible: + +# In[ ]: + + +fc.remove_region_data(region_data=my_regions[1], region_layer_id="layer1") + + +# In[ ]: diff --git a/test/test_demo_lsst-footprint.py b/test/test_demo_lsst-footprint.py new file mode 100644 index 0000000..f7257eb --- /dev/null +++ b/test/test_demo_lsst-footprint.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +# coding: utf-8 + +# # Intro + +# This notebook demonstrates basic usage of the firefly_client API method `overlay_footprints` which overlays the footprint described in image pixel of JSON format on a FITS image. +# +# Note that it may be necessary to wait for some cells (like those displaying an table) to complete before executing later cells. + +# ## Setup + +# Imports for firefly_client: + +# In[ ]: + + +from firefly_client import FireflyClient +import astropy.utils.data + +using_lab = False +url = "http://127.0.0.1:8080/firefly" +# FireflyClient._debug= True + + +# In this example, we use local firefly server (you can also use public server - irsaviewer instead): + +# Instantiate `FireflyClient` using the url above. + +# In[ ]: + + +fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) + + +# ## Start with Image Data + +# The data used in this example are taken from http://web.ipac.caltech.edu/staff/shupe/firefly_testdata and can be downloaded. Download a file using the image URL below and upload it to the server: + +# In[ ]: + + +image_url = ( + "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits" +) +filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120) +imval = fc.upload_file(filename) + + +# Set the `plot_id` for the image and display it through the opened browser: + +# In[ ]: + + +plotid = "footprinttest" +status = fc.show_fits( + file_on_server=imval, plot_id=plotid, title="footprints HSC R-band" +) + + +# ## Add Data for Footprints + +# Upload a table with footprint data using the foloowing url from where it can be downloaded: + +# In[ ]: + + +table_url = "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml" +footprint_table = astropy.utils.data.download_file(table_url, cache=True, timeout=120) +tableval = fc.upload_file(footprint_table) + + +# Overlay a footprint layer (while comparing a highlighted section to the overall footprint) onto the plot using the above table: + +# In[ ]: + + +status = fc.overlay_footprints( + tableval, + title="footprints HSC R-band", + footprint_layer_id="footprint_layer_1", + plot_id=plotid, + highlightColor="yellow", + selectColor="cyan", + style="fill", + color="rgba(74,144,226,0.30)", +) + diff --git a/test/test_demo_region.py b/test/test_demo_region.py new file mode 100644 index 0000000..d3da321 --- /dev/null +++ b/test/test_demo_region.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# coding: utf-8 + +# # Intro + +# This notebook demonstrates basic usage of the firefly_client API involving how to add region data with text to a FITS image. +# +# Note that it may be necessary to wait for some cells (like those displaying an image) to complete before executing later cells. + +# ## Setup + +# Imports for firefly_client: + +# In[ ]: + + +from firefly_client import FireflyClient +import astropy.utils.data + +using_lab = False +url = "http://127.0.0.1:8080/firefly" +# FireflyClient._debug= True + + +# In this example, we use the IRSA Viewer application for firefly server: + +# Instantiate `FireflyClient` using the URL above: + +# In[ ]: + + +fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) + + +# ## Download some data + +# Download a sample image and table. +# +# We use astropy here for convenience, but firefly_client itself does not depend on astropy. + +# Download a cutout of a WISE band as 1 FITS image: + +# In[ ]: + + +image_url = ( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" +) +filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120) + + +# ## Display tables and catalogs + +# Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + +# In[ ]: + + +# localbrowser, browser_url = fc.launch_browser() + + +# If it does not open automatically, try using the following command to display a web browser link to click on: + +# In[ ]: + + +fc.display_url() + + +# Upload a local file to the server and then display it (while keeping in mind the `plot_id` parameter being chosen): + +# In[ ]: + + +imval = fc.upload_file(filename) +status = fc.show_fits( + file_on_server=imval, plot_id="region test", title="text region test" +) + + +# Add region data containing text region with 'textangle' property + +# In[ ]: + + +text_regions = [ + 'image;text 100 150 # color=pink text={text angle is 30 deg } edit=0 highlite=0 font="Times 16 bold normal" textangle=30', + 'image;text 100 25 # color=pink text={text angle is -20 deg } font="Times 16 bold italic" textangle=-20', + "circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan", + "image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}", + "circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \" and ' in it}", +] +status = fc.add_region_data( + region_data=text_regions, region_layer_id="layerTextRegion", plot_id="region test" +) + diff --git a/test/test_demo_show-image.py b/test/test_demo_show-image.py new file mode 100644 index 0000000..08c7ea6 --- /dev/null +++ b/test/test_demo_show-image.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +# coding: utf-8 + +# In[ ]: + + +from firefly_client import FireflyClient + +using_lab = False +url = "http://127.0.0.1:8080/firefly" +# FireflyClient._debug= True + + +# In[ ]: + + +fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) + + +# In[ ]: + + +fc.show_fits(URL="http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits") + diff --git a/test/test_plot_interface.py b/test/test_plot_interface.py new file mode 100644 index 0000000..67bb63c --- /dev/null +++ b/test/test_plot_interface.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python +# coding: utf-8 + +# This notebook demonstrates the simplified plot interface. The philosophy of the `plot` module is similar to how `import matplotlib.pyplot as plt` is used to select a backend and make simple plotting commands available, with the object-oriented interface still available for advanced applications. + +# The goal of the notebook is to easily show tables, make plots, and display images. The data items can be uploaded directly as files to Firefly, or as objects with serialization methods. + +# ## Initialize Firefly viewer + +# When the plot method is imported, a search will be conducted for an instance of `FireflyClient`: +# +# * If the current session includes an operational instance of `FireflyClient` with a web browser connected, it will be used. +# * Else, if the environment variable `FIREFLY_URL` is defined, an instance of `FireflyClient` will be created using that URL. +# * Else, if there is a Firefly server running locally at `http://localhost:8080/firefly`, it will be used. +# * Finally, a short list of public servers will be tried. + +# In[19]: + + +from firefly_client import FireflyClient +import firefly_client.plot as ffplt + +fc = FireflyClient.make_client("https://irsa.ipac.caltech.edu/irsaviewer") +ffplt.use_client(fc) + + +# The next cell will attempt to launch a browser, if there is not already a web page connected to the instance of `FireflyClient`. + +# In[2]: + + +ffplt.open_browser() + + +# If the browser launch is unsuccessful, a link will be displayed for your web browser. + +# The next cell will display a clickable link + +# In[3]: + + +ffplt.display_url() + + +# #### Optional: specifying a server and channel. +# +# By default, a channel is generated for you. If you find it more convenient to use a specific server and channel, uncomment out the next cell to generate a channel from your username. + +# In[4]: + + +# import os +# my_channel = os.environ['USER'] + '-plot-module' +# ffplt.reset_server(url='https://lsst-demo.ncsa.illinois.edu/firefly', channel=my_channel) + + +# ## Restart here + +# If needed, clear the viewer and reset the layout to the defaults. + +# In[20]: + + +ffplt.clear() + + +# In[21]: + + +ffplt.reset_layout() + + +# ### Upload a table directly from disk. + +# In[22]: + + +import astropy.utils.data + + +# For the case of a file already on disk, pass the path and a title for the table. Here we download a file. + +# In[23]: + + +m31_table_fname = astropy.utils.data.download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl", + timeout=120, + cache=True, +) + + +# Upload the table file from disk, and enable the coverage image + +# In[24]: + + +tbl_id = ffplt.upload_table(m31_table_fname, title="M31 2MASS", view_coverage=True) + + +# Make a histogram from the last uploaded table, with the minimum required parameters + +# In[25]: + + +ffplt.hist("j_m") + + +# Make a scatter plot from the last uploaded table, with the minimum required parameters + +# In[26]: + + +ffplt.scatter("h_m - k_m", "j_m") + + +# ### Viewing a Python image object + +# Load a FITS file using astropy.io.fits + +# In[12]: + + +import astropy.io.fits as fits + +m31_image_fname = astropy.utils.data.download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits", + timeout=120, + cache=True, +) +hdu = fits.open(m31_image_fname) +type(hdu) + + +# Upload to the viewer. The return value is the `plot_id`. + +# In[13]: + + +ffplt.upload_image(hdu) + + +# ### Viewing a Numpy array + +# If the `astropy` package is installed, a Numpy array can be uploaded. + +# In[14]: + + +data = hdu[0].data +type(data) + + +# Upload to the viewer. The return value is the `plot_id`. + +# In[15]: + + +ffplt.upload_image(data) + + +# ### Uploading a Python table object + +# Load a table using astropy + +# In[16]: + + +wise_tbl_name = astropy.utils.data.download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl", + timeout=120, + cache=True, +) + + +# Read the downloaded table in IPAC table format + +# In[17]: + + +from astropy.table import Table + +wise_table = Table.read(wise_tbl_name, format="ipac") + + +# Upload to the viewer with a set title + +# In[18]: + + +ffplt.upload_table(wise_table, title="WISE Demo Table") + + +# In[ ]: + + + + + +# In[ ]: + + + + From 96f55179ca84fe53c65deb8855aab8c34d5e9679 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Wed, 19 Feb 2025 16:48:46 -0800 Subject: [PATCH 05/10] Move all tests to pytest --- pyproject.toml | 6 +- test/conftest.py | 11 +- test/test_5_instances_3_channels.py | 7 +- test/test_basic_demo_tableload.py | 7 +- test/test_demo_3color.py | 7 +- test/test_demo_HiPS.py | 7 +- test/test_demo_advanced_all.py | 7 +- test/test_demo_advanced_steps.py | 7 +- test/test_demo_advanced_table_images.py | 9 +- ...test_demo_advanced_tables_images_upload.py | 9 +- test/test_demo_basic.py | 276 ++++++------------ test/test_demo_lsst-footprint.py | 87 ------ test/test_demo_lsst_footprint.py | 55 ++++ test/test_demo_region.py | 149 ++++------ test/test_demo_show-image.py | 30 +- test/test_plot_interface.py | 241 +++++---------- test/test_simple_callback.py | 36 +-- test/test_simple_callback_response.py | 37 +-- test/test_socket_not_added_until_listener.py | 37 +-- uv.lock | 2 +- wise-00.fits | Bin 0 -> 216000 bytes 21 files changed, 359 insertions(+), 668 deletions(-) delete mode 100644 test/test_demo_lsst-footprint.py create mode 100644 test/test_demo_lsst_footprint.py create mode 100644 wise-00.fits diff --git a/pyproject.toml b/pyproject.toml index e740260..c565708 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ authors = [{ name = "IPAC LSST SUIT" }] readme = "README.md" license = { file = "License.txt" } requires-python = ">=3.10" -dependencies = ["websocket-client", "requests", "astropy>=6.1.7"] +dependencies = ["websocket-client", "requests", "astropy>=6"] keywords = [ "jupyter", "firefly", @@ -96,8 +96,8 @@ exclude_lines = [ ] [tool.tox] -env_list = ["3.10", "3.11", "3.12", "3.13", "build_docs"] -requires = ["tox>=4.0", "tox-uv>=1.20", "pytest-container>=0.4.3"] +env_list = ["3.10", "3.11", "3.12", "3.13", "3.14", "build_docs"] +requires = ["tox>=4.0", "tox-uv>=1.20"] [tool.tox.env_run_base] runner = "uv-venv-lock-runner" diff --git a/test/conftest.py b/test/conftest.py index 0ad08f2..34d28e1 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,12 +1,5 @@ -from pytest_container import ( - add_logging_level_options, - auto_container_parametrize, - set_logging_level_from_cli_args, -) - - -def pytest_generate_tests(metafunc): - auto_container_parametrize(metafunc) +from pytest_container import (add_logging_level_options, + set_logging_level_from_cli_args) def pytest_addoption(parser): diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py index 186a776..255fb65 100644 --- a/test/test_5_instances_3_channels.py +++ b/test/test_5_instances_3_channels.py @@ -1,12 +1,13 @@ import time +from test.container import FIREFLY_CONTAINER import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_5_instances_3_channels(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_basic_demo_tableload.py b/test/test_basic_demo_tableload.py index b86f541..40a68c3 100644 --- a/test/test_basic_demo_tableload.py +++ b/test/test_basic_demo_tableload.py @@ -1,15 +1,16 @@ import os import time from pathlib import Path +from test.container import FIREFLY_CONTAINER from urllib import request import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_basic_tableload(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_3color.py b/test/test_demo_3color.py index 84214c8..f4c99a0 100644 --- a/test/test_demo_3color.py +++ b/test/test_demo_3color.py @@ -1,12 +1,13 @@ import time +from test.container import FIREFLY_CONTAINER import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_3color(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_HiPS.py b/test/test_demo_HiPS.py index 67254bc..7abf88e 100644 --- a/test/test_demo_HiPS.py +++ b/test/test_demo_HiPS.py @@ -1,12 +1,13 @@ import time +from test.container import FIREFLY_CONTAINER import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_hi_ps(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_advanced_all.py b/test/test_demo_advanced_all.py index 1c6ec22..5249add 100644 --- a/test/test_demo_advanced_all.py +++ b/test/test_demo_advanced_all.py @@ -1,12 +1,13 @@ import time +from test.container import FIREFLY_CONTAINER import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_advanced_steps.py b/test/test_demo_advanced_steps.py index db8b67c..9a6206b 100644 --- a/test/test_demo_advanced_steps.py +++ b/test/test_demo_advanced_steps.py @@ -1,13 +1,14 @@ import time +from test.container import FIREFLY_CONTAINER import pytest from astropy.utils.data import download_file -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv_steps(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_advanced_table_images.py b/test/test_demo_advanced_table_images.py index 491505f..27d6b8d 100644 --- a/test/test_demo_advanced_table_images.py +++ b/test/test_demo_advanced_table_images.py @@ -1,13 +1,14 @@ import time +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test import firefly_slate_demo as fs -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv_table_imgs(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_advanced_tables_images_upload.py b/test/test_demo_advanced_tables_images_upload.py index 34d782b..57953eb 100644 --- a/test/test_demo_advanced_tables_images_upload.py +++ b/test/test_demo_advanced_tables_images_upload.py @@ -1,13 +1,14 @@ import time +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER import pytest -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from test import firefly_slate_demo as fs -from test.container import FIREFLY_CONTAINER + +from firefly_client import FireflyClient -@pytest.mark.parametrize("container", [FIREFLY_CONTAINER]) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv_table_imgs_upload(container: ContainerData): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 diff --git a/test/test_demo_basic.py b/test/test_demo_basic.py index 6a1830a..cf4e71a 100644 --- a/test/test_demo_basic.py +++ b/test/test_demo_basic.py @@ -1,197 +1,89 @@ -#!/usr/bin/env python -# coding: utf-8 +import time +from test.container import FIREFLY_CONTAINER -# # Intro +import pytest +from astropy.utils.data import download_file +from pytest_container.container import ContainerData -# This notebook demonstrates basic usage of the firefly_client API. -# -# Note that it may be necessary to wait for some cells (like those displaying an image) to complete before executing later cells. - -# ## Setup - -# Imports for firefly_client: - -# In[ ]: - - -import astropy.utils.data from firefly_client import FireflyClient -using_lab = False -# url = 'https://irsa.ipac.caltech.edu/irsaviewer' -url = "http://127.0.0.1:8080/firefly" -# FireflyClient._debug= True - - -# The firefly_client needs to connect to a firefly server. In this example, we use the IRSA Viewer application for the server: - -# Instantiate `FireflyClient` using the URL above: - -# In[ ]: - - -fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) - - -# ## Download Sample Data - -# For the following example, we download a sample image and table by using astropy (it's used only for convenience, firefly_client itself does not depend on astropy): - -# Download a cutout of a WISE band as 1 FITS image: - -# In[ ]: - - -image_url = ( - "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" - + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" -) -filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120) - - -# Download a 2MASS catalog using an IRSA VO Table Access Protocol ([TAP](https://irsa.ipac.caltech.edu/docs/program_interface/astropy_TAP.html)) search: - -# In[ ]: - - -table_url = ( - "http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&" - + "QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec)," - + "CIRCLE('J2000',70.0,20.0,0.1))=1" -) -tablename = astropy.utils.data.download_file(table_url, timeout=120, cache=True) - - -# ## Display Tables and Catalogs - -# Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. - -# In[ ]: - - -# localbrowser, browser_url = fc.launch_browser() - - -# If it does not open automatically, try using the following command to display a web browser link to click on: - -# In[ ]: - - -fc.display_url() - - -# Alternatively, uncomment the lines below (while commenting out the 2 lines above) to display the browser application in the notebook: - -# In[ ]: - - -# from IPython.display import IFrame -# print('url: %s' % fc.get_firefly_url()) -# IFrame(fc.get_firefly_url(), 1100, 1000) - - -# The current example involves 2 images in FITS format to be viewed through the chosen browser, both in different ways. -# For the 1st method, upload a local file to the server and then display it: - -# In[ ]: - - -imval = fc.upload_file(filename) -status = fc.show_fits(file_on_server=imval, plot_id="wise-cutout", title="WISE Cutout") - - -# For the 2nd method, pull an image directly from a URL: - -# In[ ]: - - -status = fc.show_fits( - file_on_server=None, - plot_id="wise-fullimage", - URL="http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" - + "/149/02206a149-w1-int-1b.fits", -) - - -# Upload a catalog table (with coordinates) to the server so it will overlay the image with markers by default. Note that markers can be visible on both "wise-cutout" and "wise-fullimage" since they would include the same field of view: - -# In[ ]: - - -file = fc.upload_file(tablename) -status = fc.show_table(file, tbl_id="tablemass", title="My 2MASS Catalog", page_size=50) - - -# Add an xy plot using the uploaded table data: - -# In[ ]: - - -status = fc.show_xyplot(tbl_id="tablemass", xCol="j_m", yCol="h_m-k_m") - - -# Alternatively, more generic method show_chart() can be used to create the same plot - -# In[ ]: - - -# trace0 = {'tbl_id': 'tablemass', 'x': "tables::j_m", 'y': "tables::h_m-k_m", -# 'type' : 'scatter', 'mode': 'markers'} -# status = fc.show_chart(data=[trace0]) - - -# Zoom into the full image by a factor of 2 (note the `plot_id` parameter we used earlier) - -# In[ ]: - - -status = fc.set_zoom("wise-fullimage", 2) - - -# Pan the full image to center on a given celestial coordinate: - -# In[ ]: - - -status = fc.set_pan("wise-fullimage", x=70, y=20, coord="J2000") - - -# Set the stretch for the full image based on IRAF zscale interval with a linear algorithm: - -# In[ ]: - - -status = fc.set_stretch("wise-fullimage", stype="zscale", algorithm="linear") - - -# Change color of the image: - -# In[ ]: - - -status = fc.set_color("wise-fullimage", colormap_id=6, bias=0.6, contrast=1.5) - - -# Add region data to the cutout image (2 areas to begin with): - -# In[ ]: - - -my_regions = [ - "image;polygon 125 25 160 195 150 150 #color=cyan", - "icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}", -] -status = fc.add_region_data( - region_data=my_regions, region_layer_id="layer1", plot_id="wise-cutout" -) - - -# Remove the second region whlie keeping the first one visible: - -# In[ ]: - - -fc.remove_region_data(region_data=my_regions[1], region_layer_id="layer1") - -# In[ ]: +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_basics(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + # ## Download Sample Data + # For the following example, we download a sample image and table by using astropy (it's used only for convenience, firefly_client itself does not depend on astropy): + # Download a cutout of a WISE band as 1 FITS image: + image_url = ( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + filename = download_file(image_url, cache=True, timeout=120) + # Download a 2MASS catalog using an IRSA VO Table Access Protocol ([TAP](https://irsa.ipac.caltech.edu/docs/program_interface/astropy_TAP.html)) search: + table_url = ( + "http://irsa.ipac.caltech.edu/TAP/sync?FORMAT=IPAC_TABLE&" + + "QUERY=SELECT+*+FROM+fp_psc+WHERE+CONTAINS(POINT('J2000',ra,dec)," + + "CIRCLE('J2000',70.0,20.0,0.1))=1" + ) + tablename = download_file(table_url, timeout=120, cache=True) + # ## Display Tables and Catalogs + # Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + # localbrowser, browser_url = fc.launch_browser() + # If it does not open automatically, try using the following command to display a web browser link to click on: + fc.display_url() + # Alternatively, uncomment the lines below (while commenting out the 2 lines above) to display the browser application in the notebook: + # from IPython.display import IFrame + # print('url: %s' % fc.get_firefly_url()) + # IFrame(fc.get_firefly_url(), 1100, 1000) + # The current example involves 2 images in FITS format to be viewed through the chosen browser, both in different ways. + # For the 1st method, upload a local file to the server and then display it: + imval = fc.upload_file(filename) + status = fc.show_fits( + file_on_server=imval, plot_id="wise-cutout", title="WISE Cutout" + ) + assert status is not None + # For the 2nd method, pull an image directly from a URL: + status = fc.show_fits( + file_on_server=None, + plot_id="wise-fullimage", + URL="http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits", + ) + # Upload a catalog table (with coordinates) to the server so it will overlay the image with markers by default. Note that markers can be visible on both "wise-cutout" and "wise-fullimage" since they would include the same field of view: + file = fc.upload_file(tablename) + status = fc.show_table( + file, tbl_id="tablemass", title="My 2MASS Catalog", page_size=50 + ) + # Add an xy plot using the uploaded table data: + status = fc.show_xyplot(tbl_id="tablemass", xCol="j_m", yCol="h_m-k_m") + # Alternatively, more generic method show_chart() can be used to create the same plot + # trace0 = {'tbl_id': 'tablemass', 'x': "tables::j_m", 'y': "tables::h_m-k_m", + # 'type' : 'scatter', 'mode': 'markers'} + # status = fc.show_chart(data=[trace0]) + # Zoom into the full image by a factor of 2 (note the `plot_id` parameter we used earlier) + status = fc.set_zoom("wise-fullimage", 2) + # Pan the full image to center on a given celestial coordinate: + status = fc.set_pan("wise-fullimage", x=70, y=20, coord="J2000") + # Set the stretch for the full image based on IRAF zscale interval with a linear algorithm: + status = fc.set_stretch("wise-fullimage", stype="zscale", algorithm="linear") + # Change color of the image: + status = fc.set_color("wise-fullimage", colormap_id=6, bias=0.6, contrast=1.5) + # Add region data to the cutout image (2 areas to begin with): + my_regions = [ + "image;polygon 125 25 160 195 150 150 #color=cyan", + "icrs;circle 69.95d 20d 30i # color=orange text={region 5/7}", + ] + status = fc.add_region_data( + region_data=my_regions, region_layer_id="layer1", plot_id="wise-cutout" + ) + # Remove the second region while keeping the first one visible: + fc.remove_region_data(region_data=my_regions[1], region_layer_id="layer1") diff --git a/test/test_demo_lsst-footprint.py b/test/test_demo_lsst-footprint.py deleted file mode 100644 index f7257eb..0000000 --- a/test/test_demo_lsst-footprint.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -# # Intro - -# This notebook demonstrates basic usage of the firefly_client API method `overlay_footprints` which overlays the footprint described in image pixel of JSON format on a FITS image. -# -# Note that it may be necessary to wait for some cells (like those displaying an table) to complete before executing later cells. - -# ## Setup - -# Imports for firefly_client: - -# In[ ]: - - -from firefly_client import FireflyClient -import astropy.utils.data - -using_lab = False -url = "http://127.0.0.1:8080/firefly" -# FireflyClient._debug= True - - -# In this example, we use local firefly server (you can also use public server - irsaviewer instead): - -# Instantiate `FireflyClient` using the url above. - -# In[ ]: - - -fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) - - -# ## Start with Image Data - -# The data used in this example are taken from http://web.ipac.caltech.edu/staff/shupe/firefly_testdata and can be downloaded. Download a file using the image URL below and upload it to the server: - -# In[ ]: - - -image_url = ( - "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits" -) -filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120) -imval = fc.upload_file(filename) - - -# Set the `plot_id` for the image and display it through the opened browser: - -# In[ ]: - - -plotid = "footprinttest" -status = fc.show_fits( - file_on_server=imval, plot_id=plotid, title="footprints HSC R-band" -) - - -# ## Add Data for Footprints - -# Upload a table with footprint data using the foloowing url from where it can be downloaded: - -# In[ ]: - - -table_url = "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml" -footprint_table = astropy.utils.data.download_file(table_url, cache=True, timeout=120) -tableval = fc.upload_file(footprint_table) - - -# Overlay a footprint layer (while comparing a highlighted section to the overall footprint) onto the plot using the above table: - -# In[ ]: - - -status = fc.overlay_footprints( - tableval, - title="footprints HSC R-band", - footprint_layer_id="footprint_layer_1", - plot_id=plotid, - highlightColor="yellow", - selectColor="cyan", - style="fill", - color="rgba(74,144,226,0.30)", -) - diff --git a/test/test_demo_lsst_footprint.py b/test/test_demo_lsst_footprint.py new file mode 100644 index 0000000..a5ba2fd --- /dev/null +++ b/test/test_demo_lsst_footprint.py @@ -0,0 +1,55 @@ +import time +from test.container import FIREFLY_CONTAINER + +import pytest +from astropy.utils.data import download_file +from pytest_container.container import ContainerData + +from firefly_client import FireflyClient + + +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_lsst_footprint(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client( + url=host, launch_browser=False + ) # ## Start with Image Data + + # The data used in this example are taken from http://web.ipac.caltech.edu/staff/shupe/firefly_testdata and can be downloaded. Download a file using the image URL below and upload it to the server: + image_url = "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/calexp-subset-HSC-R.fits" + filename = download_file(image_url, cache=True, timeout=120) + imval = fc.upload_file(filename) + + # Set the `plot_id` for the image and display it through the opened browser: + plotid = "footprinttest" + status = fc.show_fits( + file_on_server=imval, plot_id=plotid, title="footprints HSC R-band" + ) + + # ## Add Data for Footprints + + # Upload a table with footprint data using the following url from where it can be downloaded: + table_url = "http://web.ipac.caltech.edu/staff/shupe/firefly_testdata/footprints-subset-HSC-R.xml" + footprint_table = download_file(table_url, cache=True, timeout=120) + tableval = fc.upload_file(footprint_table) + + # Overlay a footprint layer (while comparing a highlighted section to the overall footprint) onto the plot using the above table: + status = fc.overlay_footprints( + tableval, + title="footprints HSC R-band", + footprint_layer_id="footprint_layer_1", + plot_id=plotid, + highlightColor="yellow", + selectColor="cyan", + style="fill", + color="rgba(74,144,226,0.30)", + ) + assert status is not None diff --git a/test/test_demo_region.py b/test/test_demo_region.py index d3da321..25400c9 100644 --- a/test/test_demo_region.py +++ b/test/test_demo_region.py @@ -1,97 +1,62 @@ -#!/usr/bin/env python -# coding: utf-8 - -# # Intro - -# This notebook demonstrates basic usage of the firefly_client API involving how to add region data with text to a FITS image. -# -# Note that it may be necessary to wait for some cells (like those displaying an image) to complete before executing later cells. - -# ## Setup - -# Imports for firefly_client: - -# In[ ]: +import time +from test.container import FIREFLY_CONTAINER +import pytest +from astropy.utils.data import download_file +from pytest_container.container import ContainerData from firefly_client import FireflyClient -import astropy.utils.data - -using_lab = False -url = "http://127.0.0.1:8080/firefly" -# FireflyClient._debug= True - - -# In this example, we use the IRSA Viewer application for firefly server: - -# Instantiate `FireflyClient` using the URL above: - -# In[ ]: - - -fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) - - -# ## Download some data - -# Download a sample image and table. -# -# We use astropy here for convenience, but firefly_client itself does not depend on astropy. - -# Download a cutout of a WISE band as 1 FITS image: - -# In[ ]: - - -image_url = ( - "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" - + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" -) -filename = astropy.utils.data.download_file(image_url, cache=True, timeout=120) - - -# ## Display tables and catalogs - -# Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. - -# In[ ]: - - -# localbrowser, browser_url = fc.launch_browser() - - -# If it does not open automatically, try using the following command to display a web browser link to click on: - -# In[ ]: - - -fc.display_url() - - -# Upload a local file to the server and then display it (while keeping in mind the `plot_id` parameter being chosen): - -# In[ ]: - - -imval = fc.upload_file(filename) -status = fc.show_fits( - file_on_server=imval, plot_id="region test", title="text region test" -) - - -# Add region data containing text region with 'textangle' property - -# In[ ]: - -text_regions = [ - 'image;text 100 150 # color=pink text={text angle is 30 deg } edit=0 highlite=0 font="Times 16 bold normal" textangle=30', - 'image;text 100 25 # color=pink text={text angle is -20 deg } font="Times 16 bold italic" textangle=-20', - "circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan", - "image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}", - "circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \" and ' in it}", -] -status = fc.add_region_data( - region_data=text_regions, region_layer_id="layerTextRegion", plot_id="region test" -) +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_region(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + + time.sleep(5) + + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + + fc = FireflyClient.make_client(url=host, launch_browser=False) + + # Download a sample image and table. + # + # We use astropy here for convenience, but firefly_client itself does not depend on astropy. + + # Download a cutout of a WISE band as 1 FITS image: + image_url = ( + "http://irsa.ipac.caltech.edu/ibe/data/wise/allsky/4band_p1bm_frm/6a/02206a" + + "/149/02206a149-w1-int-1b.fits?center=70,20&size=200pix" + ) + filename = download_file(image_url, cache=True, timeout=120) + + # ## Display tables and catalogs + + # Instantiating FireflyClient should open a browser to the firefly server in a new tab. You can also achieve this using the following method. The browser open only works when running the notebook locally, otherwise a link is displayed. + # localbrowser, browser_url = fc.launch_browser() + + # If it does not open automatically, try using the following command to display a web browser link to click on: + fc.display_url() + + # Upload a local file to the server and then display it (while keeping in mind the `plot_id` parameter being chosen): + imval = fc.upload_file(filename) + status = fc.show_fits( + file_on_server=imval, plot_id="region test", title="text region test" + ) + + # Add region data containing text region with 'textangle' property + text_regions = [ + 'image;text 100 150 # color=pink text={text angle is 30 deg } edit=0 highlite=0 font="Times 16 bold normal" textangle=30', + 'image;text 100 25 # color=pink text={text angle is -20 deg } font="Times 16 bold italic" textangle=-20', + "circle 80.48446937 77.231180603 13.9268922 # text={physical;circle ;; color=cyan } color=cyan", + "image;box 100 150 60 30 30 # color=red text={box on # J2000 with size w=60 & h=30}", + "circle 80.484469p 77.231180p 3.002392 # color=#B8E986 text={This message has both a \" and ' in it}", + ] + status = fc.add_region_data( + region_data=text_regions, + region_layer_id="layerTextRegion", + plot_id="region test", + ) + assert status is not None diff --git a/test/test_demo_show-image.py b/test/test_demo_show-image.py index 08c7ea6..2995d8c 100644 --- a/test/test_demo_show-image.py +++ b/test/test_demo_show-image.py @@ -1,24 +1,22 @@ -#!/usr/bin/env python -# coding: utf-8 - -# In[ ]: +import time +from test.container import FIREFLY_CONTAINER +import pytest +from pytest_container.container import ContainerData from firefly_client import FireflyClient -using_lab = False -url = "http://127.0.0.1:8080/firefly" -# FireflyClient._debug= True - - -# In[ ]: - - -fc = FireflyClient.make_lab_client() if using_lab else FireflyClient.make_client(url) - -# In[ ]: +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_show_img(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + time.sleep(5) -fc.show_fits(URL="http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits") + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + fc = FireflyClient.make_client(url=host, launch_browser=False) + fc.show_fits(URL="http://web.ipac.caltech.edu/staff/roby/demo/wise-m51-band2.fits") diff --git a/test/test_plot_interface.py b/test/test_plot_interface.py index 67bb63c..f7b28f3 100644 --- a/test/test_plot_interface.py +++ b/test/test_plot_interface.py @@ -1,204 +1,115 @@ -#!/usr/bin/env python -# coding: utf-8 - -# This notebook demonstrates the simplified plot interface. The philosophy of the `plot` module is similar to how `import matplotlib.pyplot as plt` is used to select a backend and make simple plotting commands available, with the object-oriented interface still available for advanced applications. - -# The goal of the notebook is to easily show tables, make plots, and display images. The data items can be uploaded directly as files to Firefly, or as objects with serialization methods. - -# ## Initialize Firefly viewer - -# When the plot method is imported, a search will be conducted for an instance of `FireflyClient`: -# -# * If the current session includes an operational instance of `FireflyClient` with a web browser connected, it will be used. -# * Else, if the environment variable `FIREFLY_URL` is defined, an instance of `FireflyClient` will be created using that URL. -# * Else, if there is a Firefly server running locally at `http://localhost:8080/firefly`, it will be used. -# * Finally, a short list of public servers will be tried. - -# In[19]: +import time +from test.container import FIREFLY_CONTAINER +import pytest +from astropy.io import fits +from astropy.table import Table +from astropy.utils.data import download_file +from pytest_container.container import ContainerData from firefly_client import FireflyClient -import firefly_client.plot as ffplt - -fc = FireflyClient.make_client("https://irsa.ipac.caltech.edu/irsaviewer") -ffplt.use_client(fc) - - -# The next cell will attempt to launch a browser, if there is not already a web page connected to the instance of `FireflyClient`. - -# In[2]: - - -ffplt.open_browser() - - -# If the browser launch is unsuccessful, a link will be displayed for your web browser. - -# The next cell will display a clickable link - -# In[3]: - - -ffplt.display_url() - - -# #### Optional: specifying a server and channel. -# -# By default, a channel is generated for you. If you find it more convenient to use a specific server and channel, uncomment out the next cell to generate a channel from your username. - -# In[4]: - - -# import os -# my_channel = os.environ['USER'] + '-plot-module' -# ffplt.reset_server(url='https://lsst-demo.ncsa.illinois.edu/firefly', channel=my_channel) - - -# ## Restart here - -# If needed, clear the viewer and reset the layout to the defaults. - -# In[20]: - - -ffplt.clear() - - -# In[21]: - - -ffplt.reset_layout() - - -# ### Upload a table directly from disk. - -# In[22]: - +from firefly_client import plot as ffplt -import astropy.utils.data +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_plt(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" -# For the case of a file already on disk, pass the path and a title for the table. Here we download a file. + time.sleep(5) -# In[23]: + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + fc = FireflyClient.make_client(url=host, launch_browser=False) -m31_table_fname = astropy.utils.data.download_file( - "http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl", - timeout=120, - cache=True, -) + ffplt.use_client(fc) + # The next cell will attempt to launch a browser, if there is not already a web page connected to the instance of `FireflyClient`. + ffplt.open_browser() -# Upload the table file from disk, and enable the coverage image + # If the browser launch is unsuccessful, a link will be displayed for your web browser. -# In[24]: + # The next cell will display a clickable link + ffplt.display_url() + # #### Optional: specifying a server and channel. + # + # By default, a channel is generated for you. If you find it more convenient to use a specific server and channel, uncomment out the next cell to generate a channel from your username. + # ## Restart here -tbl_id = ffplt.upload_table(m31_table_fname, title="M31 2MASS", view_coverage=True) + # If needed, clear the viewer and reset the layout to the defaults. + ffplt.clear() + ffplt.reset_layout() + # ### Upload a table directly from disk. + # For the case of a file already on disk, pass the path and a title for the table. Here we download a file. + m31_table_fname = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/m31-2mass-2412-row.tbl", + timeout=120, + cache=True, + ) -# Make a histogram from the last uploaded table, with the minimum required parameters + # Upload the table file from disk, and enable the coverage image + tbl_id = ffplt.upload_table(m31_table_fname, title="M31 2MASS", view_coverage=True) + assert tbl_id is not None -# In[25]: + # Make a histogram from the last uploaded table, with the minimum required parameters + ffplt.hist("j_m") + # Make a scatter plot from the last uploaded table, with the minimum required parameters + ffplt.scatter("h_m - k_m", "j_m") -ffplt.hist("j_m") + # ### Viewing a Python image object + # Load a FITS file using astropy.io.fits -# Make a scatter plot from the last uploaded table, with the minimum required parameters + m31_image_fname = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits", + timeout=120, + cache=True, + ) + hdu = fits.open(m31_image_fname) + type(hdu) -# In[26]: - - -ffplt.scatter("h_m - k_m", "j_m") - - -# ### Viewing a Python image object - -# Load a FITS file using astropy.io.fits - -# In[12]: - - -import astropy.io.fits as fits - -m31_image_fname = astropy.utils.data.download_file( - "http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits", - timeout=120, - cache=True, -) -hdu = fits.open(m31_image_fname) -type(hdu) - - -# Upload to the viewer. The return value is the `plot_id`. - -# In[13]: - - -ffplt.upload_image(hdu) - - -# ### Viewing a Numpy array - -# If the `astropy` package is installed, a Numpy array can be uploaded. - -# In[14]: - - -data = hdu[0].data -type(data) - - -# Upload to the viewer. The return value is the `plot_id`. - -# In[15]: - - -ffplt.upload_image(data) - - -# ### Uploading a Python table object - -# Load a table using astropy - -# In[16]: - - -wise_tbl_name = astropy.utils.data.download_file( - "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl", - timeout=120, - cache=True, -) - - -# Read the downloaded table in IPAC table format - -# In[17]: - - -from astropy.table import Table + # Upload to the viewer. The return value is the `plot_id`. -wise_table = Table.read(wise_tbl_name, format="ipac") + # In[13]: + ffplt.upload_image(hdu) -# Upload to the viewer with a set title + # ### Viewing a Numpy array -# In[18]: + # If the `astropy` package is installed, a Numpy array can be uploaded. + # In[14]: -ffplt.upload_table(wise_table, title="WISE Demo Table") + data = hdu[0].data + type(data) + # Upload to the viewer. The return value is the `plot_id`. -# In[ ]: + # In[15]: + ffplt.upload_image(data) + # ### Uploading a Python table object + # Load a table using astropy + # In[16]: -# In[ ]: + wise_tbl_name = download_file( + "http://web.ipac.caltech.edu/staff/roby/demo/WiseDemoTable.tbl", + timeout=120, + cache=True, + ) + # Read the downloaded table in IPAC table format + wise_table = Table.read(wise_tbl_name, format="ipac") + # Upload to the viewer with a set title + ffplt.upload_table(wise_table, title="WISE Demo Table") diff --git a/test/test_simple_callback.py b/test/test_simple_callback.py index 913ae60..d8a5601 100644 --- a/test/test_simple_callback.py +++ b/test/test_simple_callback.py @@ -1,36 +1,22 @@ -import random import time +from test.container import FIREFLY_CONTAINER -from firefly_client import FireflyClient -from pytest_container.container import Container, ContainerData, EntrypointSelection -from pytest_container.inspect import NetworkProtocol, PortForwarding - -FIREFLY_CONTAINER = Container( - url="docker.io/ipac/firefly:latest", - extra_launch_args=["--memory=4g"], - entry_point=EntrypointSelection.AUTO, - forwarded_ports=[ - PortForwarding( - container_port=8080, - protocol=NetworkProtocol.TCP, - host_port=random.randint(8000, 65534), - bind_ip="127.0.0.1", - ) - ], -) +import pytest +from pytest_container.container import ContainerData -CONTAINER_IMAGES = [FIREFLY_CONTAINER] +from firefly_client import FireflyClient -def test_simple_callback(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port >= 8000 - assert auto_container.forwarded_ports[0].host_port <= 65534 - assert auto_container.forwarded_ports[0].container_port == 8080 - assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_simple_callback(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" time.sleep(5) - host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" channel1 = "channel-test-1" FireflyClient._debug = False fc = FireflyClient.make_client( diff --git a/test/test_simple_callback_response.py b/test/test_simple_callback_response.py index f7910ce..ed12da9 100644 --- a/test/test_simple_callback_response.py +++ b/test/test_simple_callback_response.py @@ -1,39 +1,24 @@ -import random import time +from test.container import FIREFLY_CONTAINER -from firefly_client import FireflyClient -from pytest_container.container import Container, ContainerData, EntrypointSelection -from pytest_container.inspect import NetworkProtocol, PortForwarding - +import pytest +from pytest_container.container import ContainerData -FIREFLY_CONTAINER = Container( - url="docker.io/ipac/firefly:latest", - extra_launch_args=["--memory=4g"], - entry_point=EntrypointSelection.AUTO, - forwarded_ports=[ - PortForwarding( - container_port=8080, - protocol=NetworkProtocol.TCP, - host_port=random.randint(8000, 65534), - bind_ip="127.0.0.1", - ) - ], -) - -CONTAINER_IMAGES = [FIREFLY_CONTAINER] +from firefly_client import FireflyClient -def test_simple_callback_response(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port >= 8000 - assert auto_container.forwarded_ports[0].host_port <= 65534 - assert auto_container.forwarded_ports[0].container_port == 8080 - assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_simple_callback_response(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" time.sleep(5) FireflyClient._debug = False token = None - host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" # fc = FireflyClient.make_client(host, channel_override=channel1, launch_browser=True, token=token) fc = FireflyClient.make_client(host, launch_browser=False, token=token) print(fc.get_firefly_url()) diff --git a/test/test_socket_not_added_until_listener.py b/test/test_socket_not_added_until_listener.py index 475bacf..2a8f249 100644 --- a/test/test_socket_not_added_until_listener.py +++ b/test/test_socket_not_added_until_listener.py @@ -1,37 +1,22 @@ -import random import time +from test.container import FIREFLY_CONTAINER -from firefly_client import FireflyClient -from pytest_container.container import Container, ContainerData, EntrypointSelection -from pytest_container.inspect import NetworkProtocol, PortForwarding - +import pytest +from pytest_container.container import ContainerData -FIREFLY_CONTAINER = Container( - url="docker.io/ipac/firefly:latest", - extra_launch_args=["--memory=4g"], - entry_point=EntrypointSelection.AUTO, - forwarded_ports=[ - PortForwarding( - container_port=8080, - protocol=NetworkProtocol.TCP, - host_port=random.randint(8000, 65534), - bind_ip="127.0.0.1", - ) - ], -) - -CONTAINER_IMAGES = [FIREFLY_CONTAINER] +from firefly_client import FireflyClient -def test_socket_not_added_until_listener(auto_container: ContainerData): - assert auto_container.forwarded_ports[0].host_port >= 8000 - assert auto_container.forwarded_ports[0].host_port <= 65534 - assert auto_container.forwarded_ports[0].container_port == 8080 - assert auto_container.forwarded_ports[0].bind_ip == "127.0.0.1" +@pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) +def test_socket_not_added_until_listener(container: ContainerData): + assert container.forwarded_ports[0].host_port >= 8000 + assert container.forwarded_ports[0].host_port <= 65534 + assert container.forwarded_ports[0].container_port == 8080 + assert container.forwarded_ports[0].bind_ip == "127.0.0.1" time.sleep(5) - host = f"http://{auto_container.forwarded_ports[0].bind_ip}:{auto_container.forwarded_ports[0].host_port}/firefly" + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" channel1 = "channel-test-1" diff --git a/uv.lock b/uv.lock index f32b6ab..2058e34 100644 --- a/uv.lock +++ b/uv.lock @@ -642,7 +642,7 @@ test = [ [package.metadata] requires-dist = [ - { name = "astropy", specifier = ">=6.1.7" }, + { name = "astropy", specifier = ">=6" }, { name = "myst-parser", marker = "extra == 'docs'" }, { name = "pydata-sphinx-theme", marker = "extra == 'docs'" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.4" }, diff --git a/wise-00.fits b/wise-00.fits new file mode 100644 index 0000000000000000000000000000000000000000..ffd918ae47cfcfea6eaa9035f9b57f2740daaf0f GIT binary patch literal 216000 zcmeFZcU)B2wl!+bB4Q4$WkNDV4vjhoNl-*YMA}Rg6afJ-BO;PjL`6hJOqdg9+E&|0 zs7#TIV8$F8bLcn0?tAVz-RIu#zVH3_ZuPG$C~D7Id#^Rt7<0_Id$RKcHx~ziV3go* z9v%Wi!8q?IZ-Jw;$7E}P$0C0L!omf{mI5O)Ycrv>k+Hzd-a}w4G&1}1Xj^9wH)pTE zji+y7%<)1NE%5OV7lZ}~e1ak)1Y!KMpLe9UAS`H!fAF7C{_*MxFXzep>)(#=pW`)$ z@m}H|@sFeb`crc}qrZ)3Y;643&;NJtG{@uP{$_l}Cg%U$(SQBBgO`VctNq``69}5- z!(mCJe@H}7Xh?*>JKVqN`Yi~GjP&>WYpCXT>?Tf_;Na@fJc9x)$6)^@K|aC$0_O$Z zbNvM#;ocz;VWHuXg2_uGBK;R=3j#vJ7kEbsf+7Td{sBQD95yIKFcJ}w;h`a+3ziB5 zf5!6;@e?$^9TvDWBFHylq@ExuG8JG=ht^>Ygko#*cx$xB8MxirkbX^sWnOL&U;u)jQ-<29e} zNP7pn|5?1hgU|O1{nz!ZZ)Vzb!T-mP?=$u{@hpr@{$ItjcX09ezFzcsKtiDjFB4N! zQxhvAQzIjuUwpq(5x(9Gn}1z7%?rkmm*#&=kX=)}Ki12?k7q3SKaS@<(F6aw{shgD zd9Xab??{eZ92600EzoV6S7X7{rq$#6x69hed9ueu_n!oq3THcO0R&T>Cp&ycG6Ot_ z&+?CmcA?&We*WP<3Opl11ZMyC((-;k0h7o7^LjQFni*S|n44Pg_#An24{qM;Bg2D2 z<_Z>gN6Z)SUK1AV9qIqq>7VNE;NpPE4!j?P`v?1bNBHYUnHm`Vxu%=;gQ=74CkrAB z7YqLQfFRO)?q37_@s6Xji$fDcI9^al^UfK$^!tv|9I`21Sa_)4BHu{f@gjV~gTk7D z@^9l!bax)>%o`QQYrb-meS`c%eEkLXO&n(D&tDO)CvbLyou0tXJ2=wcH*h3h1c4wl zJZLU&r93a+x3gxl;`bfx+#TTI&=ikp)JR`wsc&TNVPtG=W@c^FJk8U-2jH`|69feD zBJ>US_ii2}$M83d4B|akYl?^6_a$i0!6w>HHu@e0lSpYy?&^v|`@ zG>1)>K9bi_gl{PCe2XHO&>}+x-oCuaGI{&4hA%+%a!wzrSzp8#n(U+>6Z?+E?x%Ph!`mt;^t5UZ@ef4EMW z;OIVK@?Xa%5EwU)k5vHU_aCaQ`T9rjRb1p7*o5bR(9p;*Mt0=)_!A~hbai(a_jmCG zf~NiIFOkCio7jmb#*gJCPtJnS&=CE(!J&(r12)BTc6Iyr@q$NO)P1GDD&@SZ!fX=`DO@l=EbhDL@i@aMhDSFp@KJX8=C`XA@o zG{EL~Q(W!-60ZrnP5a+;RxdLJM*0y!a~F6E7V%yY9_h^?coz!!n~0O0r#Y~E{+IC? z8JL+fDVa9KV~H|T5X{=lo0T(zzgY|lyn{n|GX6^IP4RyEzl--zwC?6L*{(@o{pEa( zSk^UPKi;x^n<#PSzvH++^+&V(_#V&r|BHCd|6 zdeil@cbzF%;O`gY{e3t74^n6rn19ih9M5i|=b!PKW}J6Da}%MZnbjZX`~A58i0S7a z78$4?8ldkR8s#6}B!mCyF~QmOU*cIASeXfptjtV|jGM3Df5hXJyc`{z_u-np!F9sF>c6JgP4c-(KmReJe{B1I z;H*gp@}}RE|9_|Srh^2Wp1Imjc5tx&%l;zJxB6pzA%39?1QGsxaMMom=Tvb18YYw-gHhv7b8l1~(}`UqRD08Wg$6 zuUYGV=TP5fRrh^(91l(D>L2UHjAum1|5>oywKQw?H@@%B|A@!a^IwD$jQ!{F2~7+* z|1h_*v@kL?H)*_3gq+|t0@oEITyz?K%~Ce3=InLqv!@4p#e z6aO{|eAZWnoMZSK284!17>4@?82UCP2+i;5FE-Y1V(q3WH3;AY{kz1Rz;89n{brfa zgcR0a&F01**YbZ~{`U&}?-ls}_Z6Vx(aWf)Y9JNO3Zo*!S5&n8Boz;?rQ(zBR4h|b z@w7B5x?@a5Z{w->bt;Ktvq)-sm}HF_q^KW9%KTg^jcY;5i-G)mCds4$B&ls6v4Mm{ zt#zo_FOZ7+AEu&r>Qvk}n~D>RNi=90iLQH+cx5a}gfB?4vo}dnHA&*uiX;)!NIE&4 zWaloBqQ^T@JanTHwJ)SNE+qNuIVA5CLNc8|l9C@uto2AtRV4bwi$uR&Ch?u4Bpug+ zWZ54`K3++3BPGesPa|1kCz741Ai1dx$t8NEFzHE(!G}q{XAjAHuOQj2Jd$mjM>4x> zB+Wib61j*ZI$0$CJ&Po>O-b@Bjidq{l01$hiNcmQQe>qR6D+bY9^|aDyEjIzXekDo?xn; zl|j|+`Ba^9fvOuDNVU8L)pT&An!N(5&D}@!(LQu9D1;sfL+I(50D5gxPw&4Dr{8mw zXubbev^RK)PA(hJRc#}>dHsy;`kSEfavOTtl%emMTnw!I0YgfD#mE-b&>JucMq}b( ze$p8>tER#hUoiH{X-t@^kI650VS02le13Qef9;F#?fwiitWDv*>;;^3kuX!b!u*giEGDa9IXoR!UV5;8vJE!b=P-KqA&|#T z*w)D5@L~d-M=r7Z&gwLFYMdo@~^71C4ojeeGAsY!3en)EQJZzdd2-{76$G)FO<7D4%xFiA! z>$;(AxgPFZ{E8=jKjB4<1kWp4;NjD;sMehX(Xp$zJp3w7ZTyS_^0&yj+#B0p1tYUO z85!xzv3XJ=HeM~m`rpiuIy?eNp-U03{2g&#pRoGrH>^^hjAhFeh;l!G@M9VXb@Imi z>U#(rHwu17x5C@e1+xrwF+EicKiNt!wc`mY(r~B3^Ho&1E}05tbEs&gl8TKOFY}tP zwvYc_Plc%-RCxa?6BohvkWNbZ2?r4zo zQ#+D(xJL4LACd=Ek-YCblDCQBczo>27*e!Wk|HLD6qk08qQH}%T}W~C11SvM8K?0i zeGo{JV>3uxVM^k?yeFs;Np`d#$uFNs62!lc<^AEF2gz`W6did_cz2jetE#9xv=dca z*g=(_8mKysai?{GYL|VXx=HJ)?)WpRyBtGxF78wtJBVt^vZ!XI9@V%dlIo3;RCBse zb-g-OUr8rbn-!#5->H}nSZ&ZJJcru-yTs%iFKWorCY<0ei%T((*UykMsnFcI9=c;b!mw}s!EbWhty}Enw4K1EU8NgD$p& zU0Eo`_V@+PkDtS3cn&6Pz6DpU2XO6r5fc<2;Ho_g6SrN1dqNVXj!DGKJvQ(=T#0## zkqGTL2@7{mMug-5qP}j%ik5G&I<^$?Eju7(z9BXiUd1-|#n==02FHeO!^KuxansHb z^3igrjIwb5)owg43dZ9Z*0^U`hzjp~6cydVrKcu1dBX$;>~fJaeFV0jNJZueCuA%) z$7ZkD*!bIYY?w9>sW(O;dBPreQ=#BK73Aem!Lb!oXxp2L?DkM`ucK5vfU#mf zhYI_Cqk<&HW(ye=g*B3Bc{E9$+#vb!yQGY2MPnZqEl^1w9cKxGyO>F zp(1(787g^_NXo`sQhwoA&kt0x_&6z!S(2ic7Rh(HklZAhWGDBLO!oyzpBIr#pMS43 zAbD5@$#V9`DO%Q2$xS7d&gJWH$%D#O zMO4|&gQ}JfBGr&+syS~-b!}p)?q(j;Q^LN(Visix~Y zQn|;Gs>M7~SsW*oocV6f9IAO4NwrTlQ@tde?lC7mobZVrUsTYGfH%~raHlU9?a^w> zH?+&uL?=rzy7pR%ZbN54{j4rDqDAO6;5YO=eG)&keT5+zm!a9EJ#?H}!(dih7`qRE zxkebQCT)k!SYwP?xfXUyk7I0g3dXNY#RP+GaPhnfr-mfhtsI6?1KeTJ;SG$n2EgD^ z67++1K|lN<4AQ$os3w9@h&POVF2VS&FHF>5!6e)nrt_j;_EiLn%9*e#T?QMqGZ=l7 zasEgT4ntz#)MhWnxh%l={6M&j+6tF$>KI>f8ZIsiG2yK)CfW3W=L{{(?4AR^k+U(c zT`m^fGC^qlM=Z?Wk4VWfENwi5m>db#+T@$=LfIPq;Za^Jp0j!ihWuMR_|<11`Y zC1TUzE7&-995!@Wh14_4k-XwD5|`XVe2xNfviXSpxE8C;&tTQy6fD1b4~yTgL&WtQ zga*`OzGG|5-De5EbxYvW_5fxVMquVHH7c0nN4KTx>GsW3Du^K}yf~VQ4)>>`<9<|F zR6zv?G^k*tf(rHwqQcMCRQ%kYB-8y!K4=-0Da;p< zjwD)aL&br2spw}XD!LXx#S=bIab7GHAIPHOb=Ro)ZYL6X74cJ(ia%tNsHcj=K9M9X z7m#AhGE!dHOr`lYRQgLWl`>A14w_UFKZ_Laf=Q99AVp#TDSqlp@{h?R-_H1o?LhMV z{QD{9%gxMrO}}SNBe~d|N?(Ym^k^C>-3O9l-a(S> zt0!q-2uUuoj!k$+k^?>@Im-Id*^k6GDo9)$PLd0wN#@M>{+-7YQ$$KpJ1VV-rm_cF zRM9q(Di?TBwOTHzR9;lOHiYW3R#07S2dZ;SrrHHEs_poWYP%EFYG+f8vK`g*-AAhN zPpJC0zN8vcMXE0qRCBhTY9j_w-PVV6C#EOelh31v_9y9aw=VST?PYosZ%-f3w?T`i zZP2z|96J1*j?O2l(Y2}?-OhDG_m4K{)mezXHEZ!huP6+0o{SOWmO^{@A?Ur_17VCO zOg3c0?CGztcy|@nqZVRRKXs5!3T%shg}qBY>_;wyZR_?J-Aw~lUAx2dhYSdN&xiiP zVbF8C2fd|Tp}!^;1|^dq9J~}py$xZs`WG051;c1*HH@_xf7@+gmZA%b>)T*$c?71;dX z9nxb4AkDQL>-BAsI^ZLc%@!juxfTh%J7H~JFT}Z~Aoh1htUh`iG5uCyg;gOIpS^?# z;Ua|g%)tDJSOi*`!GCTRd|rq#=kjnWXzxY0XKK=I>ms`SrjZIQ%c#hz0~Jk;q{6*3 zsNn1yDwvf;1*%Baoc2^)YDp6HaFVU!{BF}TDp|*CPwxrIp9PRCx;IJ3Mv)}=3yGCl zB<`#tQCl@C4meE3@vTVIlJl@`jJr%*Dt7on#k+(g8qE3Gz{^xzW=WzkoUc77BUx-9 zl??1p%HQ*-^uZS@YbBu44eF%4TR}?gE>sfNiApRRsKl!;m8@kAy3m74det%(8Ly*` zlKjq4lAmTQ-YO#b+gGF*z&g?_k`&92Q%O5>Dye3!d%uUu`mLa{LwQuDQ%}k(%z1+t zyT96yOvL!pW6fGTkfdulXH++4eJLVwT5l3>zQK6pT(Fxb$#<)fqG~ji+-XC~${AGp z)Sk+FT%d{w=De;V&eir%O`$#2sV7oh_&KV5JDO@u7-up+s<~oHH8*BaO}GK6etAaK zZM)f_H(4Eh1=)Rs0J)CSy4Lf7$=?w#V<+p_1ZGT1I zI(nhiho8{y@L_a3+lVfLqfnb4j_zio(KBxl`X29sfj1l>*uNLUUEg42!#U`#>BJhb z1B|9Vg30h~nC>_RbL-Zy9I*!0!ZO%snqt)M!x-IhF>H!X!|MJWn7w5^`ML%MIhxR0 z(gV5~jnF+m1bT5pp}*4+26qXDuj3)~9|NI9K7{Q{AY8ZrMkmu?lBtB*J2@=(=HbU( za*UQ%0`EV<{@6=6CSAwafb$r;$`xb1qA_+u6rA>d!nno?xL)rDkM1Uz=GzBzGX3Gd z+!ld5pJ3jRU$CH!5DQyO!D8BgRWS<@x8DHCqk14M=MuImys_)?OdPB(Lf*av{Gx4* z+s{Tro_`M&Z(c*y^Bk&z2SOQDfZ_x8_;pJUoKI2U`1(P}eYzhxo1(GZFCAMMXPZi% zVB-iUtbb{O)HhP3jGT$2>|!MPlplrL= zmxb`JMF^c$j`?pHd)Iz}|MR}^-P(}~?1$3rO-^*%)0A$%OQS-at5l?YkcxZ*Ij2jd zLQ_8~_?SzD^Bz*svi>CMI-De}n3FzSB6+xkdy7O#dq@TxQY>PclA26BGZihUtRDv>;- z60<~7oP0rw{+tgkjBEB!r1=>n*7XECG9SeN>oL)Lk3ap(S1}qpFNc-=D0N!emKD2<^@>#-gBm*BrWn zMCcBj4&6O9(A%I6{gMk$Lqq>~*+PAz>%%r=;-D*|5(vhhw{)a60o2E`@jC-em@+e(Q~yr>??h$~ySx zzDA%>g!xv{2)##$+I1AG`WPb4X9|+U!?B^k8yOb|BinrqaHcO#MeI0HGk8?aq_1m|`@`p;q5Ae)JG z35`g}ks-OqHzYoriNxV^kg#Q4QGvriy4|Qr1xr~=?$}2~nU|^PQ8pE&@w3f5Dl9am zqVAeh)W9C4eKv_7dyq7&6UibQNp``OWV6nY^cZWi=hsNGY6nTSHjt#If+Uwllf-im z`;Lty3A1HiqJYH5+L0)~1Bn)NA5%e=1}gor6_vhxN2SUfD%G4v%4~jr+e1=Htx0*=k(B0o zRN}zCO9%EYcM3_W&)75Mz2x_%eWj74)OP?1}4TZOy~>Y!Dlc! z1Q@Tp3lm2Tm|FIOne}IwU0eY(olP)Feh4FBKNwt6hu*pQ(Dl0loeRgIlXez5Ph+54 z=mhFS{b~jS<#IEyL!_UD&=T5PMrX;z*bXXR40j@(JMfhSd-k3LvwPL2`C6 zihAVZ`l`9OB95HBxD`3uJ&<){Dz=zKAc1XCj9|=W`NVr#rguCaF5U?2WLnk7xzads1oQdeAud&Q%2BKzlK=|Qv2yGLI z`IkE*sI4It-es@SiZNF*oC?P?*F`O(;??FH!4`Bbub1(i%mCdFpUzYt)V1&!Je%apW~EgBt1nWyFQH+i`e_IAI-UF2P%8ilgdBkP^Dd8s#?!ncBO(; zN7%#cVMD6l&ynh25vf9=Nwv*|R5E^V(+5&@)S&9;!Teq@)g1UjwK1IA4eC$zL%rzE zx!!bdg^(TyI?=;RPpF~Fi=L^c)2qcodV5_-pOf#SMcFH~>DdkKlh&g17@*srUC>}m z^p04CezI}+!A%oGMp|Hm$yI1Q$cOIbO6VWVg~6*R7{0g);b|=x+012K*dE5F`7l1W z5ynOAQx1O*p(q}PZM~q+H3!|&Q_xX`LWe8*Is>{wC;9<&Lq|X_yEXLF?!lmQI1HS6 zz#t(22KS9%m>34(;oo2!l+1j35EgUR!Fp&{*hI5eQ&EV~td}CNH%GDd!&$6}zJZu;zF3*8fu;M}W6`}-gzKI_h(ZJNUp=OxD_oyb zQ&QpfcU07u>vdC?k!WQciE0OtD2_eIIu9y#Dxjjz?0p!lpyC?lsp@>@JQYc9cOt3t zW|Gcv<29*9l2~4sKYB5*d61;EjQ?g!Qh6B3=H!zsoxS%?jM2_*nETvGlAA$Ny%3TH zyL2tgt63KXd%Adq{5Cmt?~ZlJsmoNmKWc)RQr% zbDX4$_K+->zi%GbFtrM}mT6998(pb_RHKOsdnF>|Z96>bN}k%S zhH-iHbliG+IlX}z9ly}WqjTu@1PNNVbV9ow7U;D4HPj+bKx60x^jcYfzJ-G^(0>R7 z^VVVb{vv3lWJ33_C-ln)!q92}gzZ=lhAf9LV>^VWT0>Zn4&hBB2>pJ5VTuV1yxKy) z&0Xl0xk2Z}An4e=hIZ9?Xy0oMo%pBF%{&af91rN*9e{rNWa!`N0t4IaFxYbrhT2CU z)SLvP(~&T_Sqbx@Rj>@_JaSBTSbuH}YtH$sq`I)uQis*x>9G3bh99|3Hu~#D*fw6m z*!2N$**hMS8vHS}`z=g=ybyDgJrS_GBNpV&zltOTC9*(~shEz8~^^Z{y6{?Kr;gR~-0t z4{|QGMAnc2*u3}$r1f@3>a#E;AMTAL=HkS`!;x^A`L22p5-0wEq_$m<#Cc>gdnd`h zG9>vj2S$Iu+HN-xn+3!~-or{$BP>lf#iHtW2=|Xg=tTAv=LS&GMIjZraJ^0$%pS*3 z61!S6C$%E6mlxOBxQ2%NTJ7=3*h`O$AuIbqe)>zBtL(RV=`}MCX;eiZz_{= z4en|HRi64pmAn^JO!T6%#n(vbdx5=E#`(3f<6`%VM-gmejE)zXuzGF?x zJXoq0PGy}lsl44#s%W*3DrEhsl5@_g-mj=?&n&9$=t9+ItUtYXP_>FR=-L;g`dURb z)~rAII@LrDWStpHHES7rL(`}(*_iH3Zlrr{BI*8m<~vOfY8ZBvp8S?hFU~BX-zG`u zLz)_Wn`@0$t5={M>#k0n5Ej-zSWyOH(sKw$_J`qlYZ&TI;7_Npz&Rd^?o34VfT@T*uo-JlZ$jeAy-3OEhYdm9vAO?LY%2{!&h7y?*wYFp z+~qhMYJmI}<8kSo2`=jt;g`9Gke~h%XCKz$#KkHc-0q6qrh}0+W+XN1ipEu>^lO7;llIJkeuy_bhBcoaA$HgW z#H7E$%Ex(F_EUF6-MWehUtcP2yum#w#5L^>RJ@6EuOfRA-yO)j#eU>V<~6H_BnseK z`qc*Z{+auV(@7LAAhG{6lKjN^+Xod%2eo5<;~JOy9ul+PDfY4Dyp8j~`)^26Z%oqd zMa`O2a?6h-+8XS+xRBJI`$`75b8iXr;S2WU4Vc&5FEFo|lI*?~$;JjSmoRTCJh?XR zMbeGz-N!QT3}JjauvhuxK>q9;lJ{7_y(p{$&DxPn{V2(DxQ>wgjTD1er*_^)@*NLJ zK7+Bimg{{DtTo#+UJv#s*|W`z_gf^}n#bC-9Vus9Qn`jYRnEFbRR>N|RbFqZ-1&~m z#bs2Q$F+yTzN82bZ2!!fO?s%IosWsRfi8SO~rtVuQM0>)W5_ot{BbAzbHNRQ+uIDlm>b?dZ2$@7zV3FVc4uw7`baBbW=TG@XHkltBPS%NHDr|4Mv?}AnaVv zHSuidvu4xF_J;1F>CjoZ1KO1mXs?Wcw#OW3&pZch<}RIsw$QEAfL`ff=#OIEYOa93 z_891ov4g&=1@y-jKtH}e47ipjZ1V_4@5*3Os|K^_;fygSm^EC2S=Sd_pD5;fLoG~H z=`ib+1PiM?Sa)o|19j46jkV#bL5@awV~3pzYPlwE&B?@2}M zDHYc0m?5E06_SM8kn%nm8@8RoX8T*n>U;z_`!jJMbUu##9E8&ra-6?$9r>?s;!^J% z5hH=OKVi^QT`h<~ZXTHCo;^DYLlH5FLB#R97u`(ee*Wms|`j6{pd zNYvYpYuU_oiwBZ;=^VxnV~D*K$?PZ+-`+>!j#o)E*O5fSv`92=8j0#Zk@)&Sl4K7e zX`fd7bRx;mxg^nxByqwu5-FTWv`LM~%d! zw;t!cCYhw#tUt%GzFoq;jW_$DTBAv6N>rK_MrE&9w-ytXe~zJYc>|SKETIaUcT~}! zMU{nlRQ2o{sk8&AX4pfj$<(2mq-d%!(q#VoMm2gOs(H$M*RP)H=W{KgvJ2hYkWUZx zyHJDnEP6UvhhD@5(3^xV^ggZweZ9&x^q6;O2O&D1o{TO(zecx28)(E|La)!!=vTiL zgWC7P&}lx<{KXnN&mO~I&1@KH_`o=@3ye2>fzf&9JDV67tY$1-XTMU{0or$xq1}O? zbyx|ly=u@ZKLG7%cF-AH1>M9Y(ECNmHFU;Ya4PisK7d}`bm-lvgWk^8(3`Idy>(xq zA7~1Lr=Aers)uoW9!!dVfa#Ptn6}>o6SJN$p0=NB6haugwSmd*?l3EAfaRt#{P?IF z#@rqU`yqqjbVY#)>(eoL;1*2NSp=W@Gz1?^N7T|Ev1*VyVinI2SNjt2Bf^j{_je@G zTBJOmg$)+cae!W8U1 z`2{;xEk#C_KGGdtV1vastm}LVDQnjwIqMLThc8CbTQQOvSzGE~M)GDt^1UrczU+de zkETex(h&(OBN4xOJmNd3V{NJv;?5uB9CRjDA0LEO0lP_b^$Cei9wpJG<0N)uymhK4 z$(|?NqsVLhfGbH%&yn~ruYD8dxzCpvOYcaWkxmjN6z1l1O4b{to{aB$~-TyF{_pTt?DYc_eFyBYE09&gsUHe7zgV zeq(*9`HJ&J=FRFsB>rJIiLH}KqSe6tX_tA88^nD&2OFl-;#RA>kNPFOl?eI7!Z#@_wU6(qyhF zL}YRQOcco&PdNu@IOYRk!#D%g9iv^J-z)2uN^GUkSV zpqlxtHAmEv>MG}e=Q>f1t`^m*-=Mk?cd6d^INg~ZOLt!t&;#8JYUt=lPaSs9izAlw zW>E&c=bqHB+XK;R@nN(R+(XBiq3ELh1ZvV~bkE&^o_9W?Z}M0STt5tgyFD@DCn2;C z?t^|z6@-!NVC-N3V`(~!yK6%@(;fy!V(5Mdf=QHeKi-hlF4wqTZ{2# z)8KZ{hwBPc;B7M&^V2xLlaEI<*AZhk_CegVZCGnoi?vTU|I%O&G-5)WbRnBAWw2K?mS6p1{OB>79Y2CgJY z{|h9(bCAU4tUonwktChDhx^E+DYHnrkGUzFbG>`qPuzih_V!$hvsghQP3{vbSwZ5| z4kTWGmqaoj67^utG-n(+rjg{=29gc=M)FDfNTJO-En*xg^xJV?7w2|boGb3g3u>FcsYwEEEr zZTE~tha(>79AFQ%R#TzAZxedNTt)8!YxLigk3o`Q7-r>(7%2ix=s(F-RlNM#>DRf4UgYJ#j&~Fh6{X_ZC z&vAi%=jG6|z6Ra+*3j*!fX?N)&^gMS+0q5N4>{)>&H3U!uJ7F*3t`$b2tNix_|^o% zz3m|!y#~Ue?BnDpVD$VtOxth{SnddGx{XoyM+1xQ!oI8$PFsYSaCsG`^i;#FH=DAy>sFqR5M6MC2F`u+=M>4nGByk8NQL2LPiAW`p9&?{ZCz71i zBP`R={RlHTB%BS3OVjE4Bi&!Tf`NDO3 z?v)wBb%`FViJx|0eybpCC(n1xL&F8xMu5-+HxA@)#_B1>Dk!s~2_BL~=TA9c9C(NPhd+SM+&pPv)km@Eg z?tEhDjw|!s!DzbwFpC~Nu%V}xpXkM2PkNJ-OYfJ)Fz#y6@_`C%?rlQ*QwiuaXAZhF zTt+u1O?1ClkDfcYcV+N33>>-;0^hS3e!LU3O1DBUItPZf++T824Z`coVHkP=`j!RI zxflbj5EV4@f5wP6BcM5aAx0YOFz$SyvuZPRe>a5Q`_IsyeG&R*YoV9clWXW}pmX&o zbVgP~`|)|`w0^=hzB|y}*#>&gSHYmmc^DM(_gT;9@5z8+upbPab75%W0K;jiF#Ir( zYkgf{;^qW%tAViEJqb44A3EmOW3ZiY3J!DPFs^$yOzLwF(;P;^@6thp_RGNH=VK79 z*%dM6?#kv;bbIfll_Hq6G3C0CGj$^w~jC$J^Y1nHquu)%8= z)^)jtl&sa<8=8isR|AkVWg3!t?&9xELGmpKO;$0$Udec`$I{Q&3;EjCildg;roR)Q?cg^?kCeDQ8s%vss@tWWxpkrYZMQd z>pBl7Sv$sXCG*z-=EIe&Gu=Ho=X%0iHjZR1Q(13zAcfO?DjC52W#eMl<1{BZ-_IjA z{lH#M5#JA#%e~Fxs5By-%Fbp{`LrceezbuqIz&c$-U_!Hm%z*tTzBBg#km6~c&>6+tIntX;zMG7k0 z=|vUohEwG+AFA$iiK-uSP2qQIs@|$b)%x?Oy5|h4Ud}w28qB?7bEq!GhwAg5(Vg>g zbkC#(J&28=20s-&O>09hK1R`R{Zi?Jfi-=t?u3>nkDv|r@3;GPDmvP^qw|()=(=Yf zx}7zG2F{{a)G72ch{O-3*%&f+A4cqFd~6P5eYg__qG2%f8VG~67SKEJ9y&kSL#xj| zXioCMi14Qvv7-W-mp?-*)B-v~Sw}`BK+npQdsUu8FCq`RD#qgr_B-1hhxRfrXwR4c z?S1;tna!T(>MPJY`8)JicYuEFDCmFHg#If)zhEu&&#(tty#NL-uVL8zGlWw`FkWs2 zvn5Heysd{H*Y(G!(av$Ay%xWXJ^_wx|-Wj>a@VBav; z6{|jNz-mh)#9oU*T%!_+8ViuBb{A=i{>*tg*wMcw_E;Xo!2tps)AYp2Gg9P*d_~@r zi8y(p4#(RU;>e&;IJmbfa<7cXeg_%$%KWhVg$%p&4`cgmdt`iN?5P^D;YcRdEi6Pz zJ?l;GnNE6|jHFX?Bs-2k%8f%jqZ+mx=Hs?(#+Le65^ZCwE$5!C{yV7n zWH_&J=06?QoC7)k>BG2r%^Z{D$hnvg$pT_Y?(~TiYh0)#HJp@fxHpnDpyEv=_dzmV zid(U-{(&Sh%mYU3MJ{C>c7}CnLN4pq3X=RVgLPRrNn3Pbf0FaCMfoJJ=bq|7u3v|8 z519WFu9azUt&g$)c0Cn;4x{1@?WnkG3o8Dl9c$tiBu-gEl1aSIe}Bf_2Iq*Mc96`} zlXEuqC@l*}TFTm&`)H(@A4oQjeU*d^Qlt!|5-Y~t+(asA&9!y6b0k+TA^BIXcYll{ zxU?ue4w|rX!3N?1X9XURbP{5357d zVXZub9}k#we_1lF!uJaiwImG7jCUeh&;_eK zE+KBHHxhCzS?k?I+JbM`vUeZ0^ZjwV+gl@du|JLs&A{4yO*};nZ49oZRYz z6LzjR_Szdq73w%r`a2G1IN;!xBNjQdr1 z9gCJuBS{xil5Q#>+1X^0hs3fkZ_4X*DECA1-W|$49qR0vm)i3^KnJOWwUHu;xlfJv zeX)Wy7<+ylvq|!ZJs9Rwi4%J^-kb~Fj3nvMHzbX?K(e8>BwxyP>%Ly3^y*E@g#}d7 zrIBO@!x*DCcrOT}qK7@HD22zR;{NL8!JIqVa-ZiozCWi0e+T316X%IpTw7>6h-4p` zk5ej|dzxiFy_wUNkzxeb%ztyFlIUbA*=>2m1T-4yY0%Of7pA<9soKkMF-^039=0&o%oRhYRBH7(C_HX!g2j`JVtbHT- z>BbxzZOY#18TMAjk&^ERESnfb3A5N9r_gW>kBvm-y1LX6J>bp789k;`Dm-nvw6Nw()JVTF*m(cV0270Z~q<1@_ z>GP&}Xwl~sT6K>>>np#Zt&1hv)m=h|z#q}^(J*x0b{}1f)N@_CW_DkA$#xlt8anA@qD>p_}yuI;O3mmG%T9JM6}Y&l(t!rND?U51?7Q3M23I zhgN|Pw3E1|H|P#@#-D=r%T>^Jo(Qe7E6^&*hPG)gwCgz+ywwi6dp9t?Zb0|BC3NE^ zLbvNg=Z5W5!hO%!j98?Pp$6&>=t(;(cJZBKH~CVQRcjAO4VzNd!y@Ogj6Od3h0j28jdv2PiZQjkYVUZW-L>Pe>LS_bR5EE9DR*&?v|0|8m2l6>B<>$8 znMWnPIMzn?ZpJ5))O#A|j_&M%?qL7(73YTLBukCs`_x&pc0WpTdv}s^|A<^o$o0g7 zB(LG~i1wt?IKDR8fmFeLniXrfCeQtum6O>cy_ZK-Cfvtjz;%n?u2Ln}_bLOqmwJpR zRmK`n)rNPZn(achb68{c(4;$o`{?dPO}f8IiyqdTp(h$o=*8T6`Yq%FeK^duyL30S zxMPl1v2)NmZ#mlRu4I3u9_@<#&_OGT>v_A-`AZelvM-|h`7Y??z5@Nc2H}T{Pz<@M z!ie~H&`Jz|PUs8h8ctvyYZzF&O#Q!DRYRFtwKnn#@fTQpGJ%b= zKk(}RA?ds0a^BznlO!Py&AYvKk`)u{^(Js?&Un**ZaDz=Xy;*@b*g>FzOwK?K8s|a%U5wsxWQMcg&{P zH2g{lR{E{Q-(#$>>F-=@`@Il*(tcxqxh>*Mj^KE{2NEOWaVEbz&R*Y#a|NeyK65oL z+~47K01VTydfAE}WnI1!u2D;f$^uPHpsL?cGPbUJJy!e8T~cR*2%v zz5C>U*l{Zv+wPsk*4ic5w*4k{++2!1$6OHo*%Yx4193E#dTn=a&Y9Kp>G()`y--Q5 zrcRpo+O$V=BqNhM-J<@yx93T=F7=Gdi4WYTe#-r}1)g-qYHIx+un)VzMS~|H0qN18Hf9E zUS-{VujRhUol_&!igCW&*`C_6Sjj%an(OvUaux(gjs^8$q2$`GhY~|&?PWypy;sQN zM%=@We3Zxf?2X)Mwe>fNCNfMiyI&D?;758ZggrY_GMf-vx=x<34SU;#4U*NJ``kGL z$;#jix|!d%^0}ex-OXo6t}}I-uh_?$kuyKV@1Lk!Q5Z@=Luz7DbR~Zfxx9D3B=1y` zuTE#-ll};g%=oz-&)j zSh7#p-YJ6P+-`6QUjetRqv3wG88s<~;bGST?se|b)~`3Q&@i}v4}?elQ+N)who>d+md+afejA^2 z1+F8m!ZlbIu0<{2Ro(*R%XY!{)fM>Zm!s{1 zf$(pB2mx15qg!AVf_1K;-=C!zyzVkaHf(~>$OufE@(y!nzr(WY7Kk`jADb#xVn=ir zM4da1*p|6C9_oqH>i#&lMS+V(cX9EA6)uL9;zIQ!T&Vesi&hT&xd0bN?8ABbiqE|d z#kpQKINR$z&M3#>bX_l;`VfE355an~Di*C9Ybuod#L4B7 z-N-|-_tZ#^E$6IFNx|I;_Od9+uP%^$-+AnT8ziq8_sR?>dGcS9JgFh(aw|*<{Hf(s z+?C>bEu`c|Ds^K0r7Y)wl!tN7T)A9c-DkgBTqB=K^W<9}UHRq6IkT2}6)SJ(?!N)u ztP`l;PXWCiuhHPfDl|NxgGQMT(4@zBG&9hl5GSqQigT+TTsAF)>!x;a zyFonWy9YeGxpV)^A=csypKWpE_V>dlz>9cK1@kEuqIKm+w0$@l{&S}y;H^Gz#T`BS zCZbO!^(?mL7+(DY~Q*A2V9(xiEDpP z!}V#e@$dQrxat^zONU0{!sSAoA3GH19B&}`X*A9}IDj)YsYrT$8cC%dNUFP!Gp!F%^ z9wW&qi&%;&GmYj*TFW_-+A)&eat~%Zgi3~auBh&;BKA>B{lAku_9gz^pZNEx4N`cI z+NKAarNnKDlyd}zg}LP8HW?oB*#38T7I8C zqEbeZEA(Y{3HLnjChRNIc+CdU6VC4o`MlX<$;FGPD$a@W^%Q!KoFt=y7|U zuhy)+!~BfPbLkyiLEOKZ+&z2Ol6dN8h>_Mel+69clDUi+O$W}&4`wiTDTW+*5_eN` zVn^)NuRXZixp1B(E}BQIB`r^~cDEqTOWn23V98NUpx%?U73UxY#oL^ zv%*dCcVDSpiIgYT+~moqGIrjJ1HyQEal^w zu`+yY}9PlH*xF)TZFgw3FSti2;}9BU5e5f*SA(-ZFd>Ty2oO5Noo zcs)}SuUtutqyyYrIl;}jHS5n2uBM~d1E0a=tQIcwm%wHCA8?5n376+P;kuZ8(qS3g zZo9$l6L-b={N0GdaIxf`*uE2-vX;OpXC9n8u=X0p!4-zm0YjtE_0x28zi}AB30e&Js~JX)F~ek? zGR$3i9xLD3VdH3GDzBI0;N}t}6mG@&pAB(i=5gGa5Rbbre&hD465P1H8CT}D!1*_q zki4W9PG=3HW-JgVH+05HyF{Gad=RG&yW(_j3!Lfq0?8GPkz9EVNn7=ai;@#O)D?%^ z_9A+G4@B;rkKKl2u*>T;c28Z6C_Nt>%vy=~;sZE!uMd)Iw@8Kya~(`I^tXmf>h(%V z&7jYm`+tTZxwz)sYx)#O)(vyX>8g?36z2AH^pU~~)HGF5J9xrHO4pKK^7oPAoeFx$ zQY6nZTpl;)J^#8t@y!a>-850pj;CMSnEtvXQG1eOxJM7^EOK(&h{1O>puTvwWGcBk zu@7aYaVH#6POk%JQXldU-04(SU>#&yAHkKaN-v+lsvfqae$bkCwvx>TX??iqN8wuAEaB4|wBpuAfS&yMOn|A|eUUtH%i#>5{%rP9+559+Azy zV-I)LNS__pcXj~|Ma;pm;+II8aTFI8Pv*|#Cuz(XNEMiSxMPtsHZ2ts#+?#mH2i~)nypC668^s(0k}J};)PlyjZJhxN$1 z8yZ5+o-^rG&Zd8*NM^$~^x`a~S7@K`OrgG%^KyV%vMvl4?Yk+G zL#=e~vsmtL@lp`&ME@%_DSuG=d4CRjANM?44QI`hk~gTiJXue#tVe`AnYK>ydTb=go4?ebDL<_pE*U;oD*Ze9q7>TSUC1dIT|-1h}duz~!_aoHM^O zyTuUBX3OAIGYwAXf5Ryv8&1(<;FKExr;NsMYBLm$eK*44@K-pjcm#*zui%h!0uGNJ zz|n3XoJ_6Yym={HEvw)*Zy4MM8o>Rw5*}@S!?X8H>S2aZ<5>&ez8}!qWf5Axd5Sjm ziqUpJ653_`iw<9_(3w0Wa*WU?=mUmk4adaoTFh58K?HMOw#CNc!1pkmx-kUT&R@ZU z#Ai@_u0W=$F)~6=;O?;mT$^wg$u-3|L4R!gUP~PQnt{XfO%b;|9P!~>alDBaPI~6x z^hSLoyEH^{(MX(WIT5GYuV>wL#^K$Uad6g5MBC(JZ+<%V>boF{**5!Ex5VKI6LG?= zKF)6Kh$|-_NZNBNNgEd|X~7YawuQM4UludV&q^{()=7r4RFql7LBj2*^C#Zlwpbn) zK9sy+S}EA*CPj;zu${LHs*c}9`JTR_dx4@n@J5vMvnbQ4=j=uPE}J;aNIOxdxlw!Bf*P41 z(WD*_tr78k)1{KV+=xBPOR~FhFEk)-*^f2!>J7c9i1Luh{JVBIW#)_&H@z;n+#89@9r{gX+ zfc5z?LNen9lP_m|Z{=JZ!(Q4Yg1BiEXH?dpb%kUtPo}SZpyVt%C%G4sCGR!++eLpV z%rlUp5zNg5`(B2F6gJG0{5tv+o(0I0w^QWFwgvJeMPKq}I&hXJho@I0McrbQg!1HMJ(G}h==fU&rHn=CBhU>2raEZ)-^Wvs(N-={|s2Lo8 zSiy06B^;g)hXb!=hnSmin5zf-cYnivTsiESpI|>Z4)zzi!u}!imi|qL{ii%Q+&Ka# zW6q!_iO)DrhueUAa2G%BhOzJpybYgC)RS%5j@GfnT9otAI_f!EuQo#KGmqfcs~6hb zIg9o+Y6Q&nN012mx4VJS%Ym5}-LWDr6aTz0!M@e!k?`OZuBHkDoi&WH7h`|A=Dj3GABp@zF&JnQNLwPSZh zdGw?xgDXVYoO(`2*5Cdz>ZaQhKjd{EN}j!Vie$!L5>5JM(e^f!?AsBN6W5k`DD-!a zWS&vcEXnrab^rakX#B2o4&{4#+lk6k$@(UjXgQo3QqGVkmDFU~5CbJ%LQi+*HrC~0 zAITi2%bjhLsL7qE!VE=uI9QastVH!!6Hyc6*97@8i5M?07@_l*|0wi~_o#atxQn2+a`&?TonmS90BB>3VM(yWa zX57X^N`8oo?pkXbQW_)U54&0lT|?wQAMSUoUnPTi`qlt%{oFuA!tc5A5>_ru#(s+rg0qbq8sVD>KMe6du8k{1k zNt{VtibcJOxIJ+Oby_#cOyaEAqYv>BL&Ki{Fv)tHdyj+aR*d#*W2bM zc~|HUvLp_p$NShp;wq<3O3BI#QhM&BJe#eSvOb&T`I!)TvCB|i{^uiAI>GX02swof zU!{72mwX&)ET3%#%hx(1`MK{H>RCIWzV~G4m0g2=KNB>JSPz5q)i5-jh{h*!&~!*T znz@|BAKh7dkHcZstQjnAGhnBq1Lpyz%s)B~ACs5x?Y)scJp;6Q{t(`8v*39o9`1i8 z!S&Da+y%?wmu5jdFSF>aO$`VPX2PniRO{ik3GU+^=%w{`w`JEzapyYAu~6dVlVx?duvZ% z-^4yRuy-Be=1;?^?m@Wtb30P>r%L+qE0VUvKvGL$BsIF5q)uKgsrF9t=#7V@vhSt7 zqgJFRXU;m-N?JOv-*9@auSwQ7;s-Izp6nbcxwcWv715KN(Rrdhk}R4R?IqKSH8dhr zl={qOKh13RNpnPXh*-q}@(IHRviDIJ`g{Ym(v$e*E6NH3W=3^V2MXRhSD8%~}+g}CHCfA*Xv)Q(W6-0TXoU)qauFu6Yk^)AKi ze+?JVqL%nhNySNaHi#a`|_BmPH!O3L7XMvCjCa- z3GcJEO0P&(qq6_o8`Vnwo!+L*X2fIMcpqx3&y3FP>~nma?JUWfx?QwO$4X8o>cZM_ ze=}9m>&jX>`#(Jxb2W(#7xfQeeo?v<_M-NhnY;OOBbdb!EBOO$B!9zhDHz4vc4`5N z8}yWt_-H9zk|3q3Rq||fvXrfvA?4;C@}j~^US>MTt3n@n{o=N~EnFeh?$Qx8P8}22NWd;5zjqJnn^}Rnz5YYqbde@0<|eyoWg~tI+A|4Ri?qf;LO&YiPNe zv+p)|M>M6rdMx~E7Q;VsBf5T=hTfLVah zC%7=LhF|eW{lVMBWo{t#LR>==#f|WMa{GWJ?O;xGN?7l zT_agTIQ!kZ&g`EQ?p(w=bo-0u9C7weoYP{+y)|NPW<&0Z#+)I&IaltUCh7s~F-`e1 zbFq|V?1QJ_bO#_%K5hAM9hvHXU-RI*!NPrkQPvf4{sau9T89D{D-0MsAW1A2e-WkT48km1-1naQ1u>VbtZGe_JDr?}?-xJ=< zYw!;K3@^VTcx*igw{`k(>Ar}(-)uNIu7q9sZP@-B2b+@%V7>J>tTs=B<$rf!+3o@? zjr+oK#uiu&4utjEk+2z0Khoc4Vbgdstmmp=?S2H-+gOA5>cg(E6&%kh;OeXk&v}F3 zo7)lXe}yC9=2OVlqrlYn=<3%8oieh}F3OF3`c-%*PlH#K54?+dqjirTXg}=-0=`a1 z(6iUfo$ij2ZL%YK*GcXBsP78q>hP5iU_B!%n&E@|Hko+*Ks6Z6=Gb{5%vBw{XuiFW5En; z`&EW*?+0T0e|@pzmm0esTtMW%*RVe$3`hQag2V?$adCNb_L^d58|g~gBxcht*O5nG zljYIQ>)eQSFh{h{$Z_kiCKht9nj1`ix-oUu=Io6d z*i%_|C%21oM1S_XBKjXvMLmfe#^u5EhjTxB;4A7A59wb&#&bsQa);v{xp6e{)OqZ; z-?@Jhznn>K?m00N^L3)#PJUuwieyE<5lvDz$vhWA&b}>qeP79ZcAtA6@tp7q?v})C zrtopW0p#ns-}Nn)+m)$aW`MiSXe0mPdPr%J@ z3Y<%oaGZS$_QQ_C_U07WtcZj4q#v-FQVq+kW>CEP2*rlUP^@Ck#2bO-hZI=T`)A|W z05*Nw!w$!23xe;i)R1cjdhcHwP2&PtSz@xqxxpoaE zGy7#;-W{y*JV=j1Dt5FpL*%=|h%W7ggJyjY<1q_|Oe_(5OczH?xYKbCKT-J&r|ZQc z>0&67Rs=gk=jk~%m=Qr&|kwPl#3hQ&x) zh*r|4kW2TDmyD}+qKxN^)^wg^?mb4_ewAn@5nsPnK`)FU@&B!&3ZSm?XD3O27%l0Y z`$)RVNR;-$qUy=swmC*r##&LHA%2;*nI64a)L(P1<4mP-WdFz>NS!ff%~|V2-LG8K z4Gie}`$&%udl1HoM%78OymXii;Uby}2hns;6TiumOzT=vuc%|)F^3|xEwj*ks55IX zsy)PT>iBs**ynus7-tVr4`GcKl}hG{Q2Gd}iQTlP4x4<&&)uTFQ!N@h8)m$drwF6& zCH|{uXY$_i?gH;Ii+MlTN}M!RGKXs6B9>|86JA|3S^>?kQ5pjEJJ^ zHS+YrG%4<$B*kZzN{M+l=DmNF(pJon{ytpF2Ck8Enww>! zB{j-sYyX91O&ILrjo|clG2BMddqBR`$v9KIn2F0T=D8jl!;rj@R)^DMROMqpvJFLELg7x!Hu(22k>-%1??vewm z>M`X0c6#aW2m^e^JQ=ivDGD>&(0jKrxua5~HmiD$^88y~_^ z`VnK3ybvAd%N&R`*gD!98+Tb_U6?!mZnzSE2Q|gIt;yIh&Jdf{x?@W~7wl+JfXHqN z956eA*w5D`Jw2CMwUZ>ZnA*&voh0?`bx9k*GfLJoul7uVq&Gew>Fb6|`u%H?e({o| ze^iUI6=ye7;(-}CqFK%UMOo#*+D#Q@$zoB>7$|C|K*?-Ej{eIP(e%h=&L(F-<~gY5MX^rDidyto)9f+x z-!Mn-t7H}zNT!x`vo4(VrVw=j>uSFaYs!s$;73tDCSG%q`=}AIoN#`o(>uw(^XFYl zMYV&EqxL#epRe(Q{D3w89Sd)>4-(7K9QZ$8JjqA z(frFDx8j&&-e%wX%AQxt&&&BX^HU|g=7UA+=pxy(s0CH6k=$_d1a_Ps!%{g*o|J-K zO?YMrahAi()!f*VULekuD|<@bM_JO9MxH%U%kwg0d2#iZyzJLWUJcKcH;baAn!dssooM;8s#d<2DCJi` z2y||zLAQN9)SomEdSjlU!E|rvpNWD2vl$FmbVt)>S!l)#$d)~x!p#3FEXg0+5&L%j z>nA-u$B8Lc(38hZcBLDRb`hnerEK^KoFX!qj}PchJ;hLIKjNalV+`vkWz`Gk%@@SJ&pYay|FtX0b7H9W5bpd zto_;$D~q4tFQf7JYo;kye$K_3m;U(MGnwZG&1bfz8@4rEi(R(k5ZR_sQa|$Sk$x>C z)tTp#{LQ{MKq=|w0g`^YPBPj@NJchm#*EtOQwfq@6(i{%#)@*R(2stAHL{&O$5=Eo ze-Y~=&z!7)+gYGH2OR_NW>3)UqEM7>KI0 ziW-x zk<2@+n|hJt0Bwj*a;Li)M|~Xm_0nm~t~O_GXEbx(CrH-Ko5VPqi{{8+>O&{7-fEc< zszVPD`{9}p`f7>)d`pnW!=^FIk9Bt?M+$VVQ`5>`_EsZ>^;4wa`&TJAKb*K_mE`@R zAJ2e1!hP1?sSqicnn;fCwmgk!F2&6fq_}sg6mRJx#dkS#W?ZKx@12yS>dCY7W2L+a z{Rw8#^76TdRM{HIThBgHy{)Hw%55UGoCUtszLB3D3ZN6BK)u|)sL%7U^qR+`fma>$ zqoQH3Ko5pj&C#^?aWpq^Ld*U(FdOH|tj(pc&!7hS+G+ZAcn-#!NHbvI1))tp`oL(jqG?9Mlu@c5J}BmKE1Cb(t~KWK*KQEif1C{!@uJM!%^Ecp8_795(IqD0OYD`q+=b!9Xh+jWPY&(_ds|khWOquCY;P^I zC#dUe9m1Sj^7Iqz=;dq6tjwOoINAR!<3ux+`(;0J<_!Zy+oznKbT7#{LO)-Vlk()& zFXjU3Ftaa03VRqy;X>jnWA2jkq>qDMqyl|^$-7=EPZl$8%)V0cI!%&-j%(;eh?l1) z4e0~2lH&hdr1%Ftus^D$`1)e@y&@@j&|aQ>xhCaX>!d=PB9)ac@_I^yyql+x4~K?I z%|Bb^i|bbTcJsQ_oppeYPDj*>8V=oxwa}}-6bbB0aK7rtx%L$^tcRmR@*#A7xC;UQ)u7W*Z?p?@K&vq&%<*Jj z9>UBR%Mdt*#KHO461WfP2JdnA(b{$l+72Fy_Aj5Iqe(Ax9-e@}>Ce&a(FO!3`(fZ* zTZ~?@2-BzLVM&A2Sl6y4&$kM21y7)H^u1Q18^!MERB5|PTJ)(ONvm9NH9eoGj zpP1(O`$Z5|PMnGG%e^rBg&H$GE@9fyFie}=0W(_uz@N;vpWEJ-e!$;YdSNwI_8NgT zOO|5o#Aos#c7i;d@>L$)F(q!vvk_J?qrLM@$;h8Y425%Bo(=K#I8puJZnllRr;_`W z6X&yY!PEj%A8E~NE;>mxiOtDP`-%DtACnj%%2;OHzGTm8QY$L<5B2bC%){h9Xs#p| zZO-gW&Wl%Uh))pHFYCm*;~sZntEgK>i;CE%a_44Iu3`NZaW);5%Pib@zIK>ocIESe zIa}`CDXJCU>BSo@8998fYk}lC*ymQT7JUNwdE1hoOQHAHl)GanaTH=EV|-b2W%LDV znSohKuSF{JRfx?rsFExXe!rC{+5+0Wn!75 z*GQo*wLM3RB)`m$nbPcwJ*mxXKrYmm^JnWCDeC@Eo@NryyqzN@9fwNEbozn1^UTUG zdQ#%DKuQW}}hM5;R^~1S9=LX#VtH7*C!Ji)IP1`Cv^g<#>3mQp5K~ zBHA8Nq5UgntS{|^_S1njIh^mxzQWUS9$e;_!lC~Q*yhc27x|KT|Z zw!xAyzD6=$mx*$^6VEWdAnI=HN9n{ON{F4bAQrD7X8(GV%yvCR-RQQc^rA#Ln_rPNoGYV6eLjUAf^hmn$4Zv=7ri_8B}YF`TyNTq-HKNXD-`ze^l)l#_qd)yY zwqcU7HGn*!12aIYL>>2#IS>noy9Cgu%02L#LNcRyZ@3jAnt9|1T#}h*%g^myBbr)% z9tG#b%kiR(WQ_&!-^G1Z`;&9#-DJsbnkw1J3UUMAB_}dma{k#Q*HI%}e#3wDOU)53{?M*+zah_SygZ`i+)T;c= zx?6Q$p3I}Rb)UWztoD$iD0=S(Uy+j8)QLT1trc^&jF}-N3oE3AocOa?BPn;(lM0?q z@oIvHynePp-Vw|Fa4c17rq)XBRu}ndbxgip2$4FYMETvJ67^&&bnkYCo>yNqIDZli z!v>-eGaMRUd<>(IMfd{~Vf}_+Nqhs1C1Qb8zuB7F>Hx~Xs zmZD8XJbcXPwc8W}$MizjH69G>kSVa-G60IBH=yX=4GQB`?0E~J=u3R%5%HQ3SN=T} zmfM@ca?f+}3`1b~^g4ZjwXmF650=dv!_p}lmMNAAZc zOMi?_SceJAuVC`&@0hxoXBy=s$ph*e9wzLRRO&R-UtQ-eM?O31CeL7PE~*?a`crcx zlUm)(`QgT<~t7*%}eSwXDWHUT5(_E%w`cPn%}#r3o#P)Sngp4HlpK;GD^xIIM+aOiGmuXXdbMND$?$2+pb8yG-;&6KpP7 zEy-v1XHPaG?s#vOXl`$&AD+15n8A|OWs_(K&}+8w`>Au16+-UKd6j5y4CQ(1EuNBmemrFjc|AGV5 z&>ft>GbMRu`AjD%%t~Z7XDI!3)MggXll!eDbxD&vX2x z_GylMxe-sTw2u7f7b3s*xuKr!Md(H@hMv(HG~kTXaOgTT8kB^_$3Sn;6#T(#XA@?F zC`R3by(#yk5-<2xm&1RH7XphGqq}7>&&Ya)?#4S2n0guh%VVh9s9X0Rw255>Hbu#6+Ox0!sNYiBsR52jx)7;ZWf;1=5suGx8T{;&*=tGB?uMG$P8 z8Bq_z{cwvOY(@Zf22e8N|vRdpM*=J|VVSLo5hw*Z0O z9_Ufo2m|_`#;A|iFy-Vm%&}}w@7@C}+Z%?zymhhM-vLYhsD}k@HegoEDVW;10-=3B zVWjsm3>o(g1JbvlU&jtSC-*e^L|sRpm~rSke*^k8X^#Ob`eN_}dki;n!N|A;7^P0Z zsHi9Ma0Ac2dm15WcZoaB9w^E%KjMwUMKy=JQTrA26BrV^W3L*hrtf7MvkyeH?be9) zk(XrmETCqE_x>FT%*pd$PUR)htXj%5y2y*=M^NYGM*hu5)Mq&#=4q)T_G7*fb#mbl3GWi~wBR?OM zLg&O)=ytz^`Zo@tK?ml!7Z$-F=NTIN&PLOmg=l`K7RC{Uu(%&fz2pM8JD!4{iy=BU zXoqg9Z3v!UfIcM!=)E!?L91_}i}4<`Ys?(>q&9HfSVH~tdf1eXgVoR&DDG)tk)I2T zsd})O7zB$feeQRop!m`YikB~;7~2*W=MKTbw<*la@?d^q56lHRrUiz|8d5E*6+~g@FDcFDL_!vYIMu@ z1T=|2_#GfKkN?jGB4|QS^t`njy>B@qLt zqFhBT{pWXH>+^_r#7ic9+nRt>UZ*LN6@FW?&aq$ItB|asbG$y;2dMYbmarBh>v-9Eha`tzk!+_@yu4EMKg$4UqkA;wuJGTji9db0MF%N&-qL|zp_G9(|sgk z+U zr~K=qD7oXQO~?lx)plHq;GZ&3JVwEwqXv9+A#1W?Ew9u1|RE{wjO2+@2Er?=IO{$18(or7fEULe|2S0p>4o8*}1vG2{3oMiSs_NB*x zov2S?2H<4oV7w)E*?*`M_6sE^U`22KFDZP-8S=T8Wqd?XMNPm7>fFB4?u6fA@p6h z!od773>SVu(+0iK?BQ9o>^lMGGdI9?K_c8d>Fv%BMCbFL5Of3Rd$k4wx=%*`iMP?) zJqP%s2L3@y;OpxE_XUsP_8k{o<~YLiBib0ED86O0zFRftX z{1rBze!k12&7Pw;syfZ_Hsh4WQ<; zsxw?${0-O2V{psc43A}Z;O)!I(8~wW_VRFawCKg&XM^s?yCC>s8A56oVLtG4 zgU0W`fNw|8Z}|uG@!W@==!R|~Q3!nIjDP{D=(MmO&r%_Fn$QjYSDK>ZOiOgyTZ(`{ zzUIEKz^G(&zc&^=4ELZ%15=(~T_Wl9%48Vcm5ht2qV$XV|NX*`ZdYc(mFKA3XuPQ>{?-=?mE_{$vjrw;QZ zb3L!&M1RqG|B~z_QIccA+EeiQ-O3)~IZZN47m!zol#G_`CB5%%NzY=wOQn^ln(0x$ z#hPf$8cyWw_?vjWQv`XwT4qEyXHEn;x0YT!`nOE0{=Bh4d zN)7!4*F`ggyW#sL^c3urOz#4E)+^Z;`;*VpirSU@M05+ua^Q@q&-;k}SMui6p}dPD zKS{2BYK3TSMeyt)>SfN8pG(q}oMF+DbBfyR^Hq{_mi~w*H|6o3f6kJZr|-&3;<_)t<;km|m*ll|b9uYlNve0G%cs7%@>xG! zKJR@apC{GHXFk3*{-k_sl`M6Wd&=)UHK^xb3*CQaqkhS3G??5S4F^m@qy1@UQeO{7 z|9rw9X*d#C%6f6yuZ9sJnU zJ;|jxhnB!jy$jaVBwHFRWUY;X`96D?GfUIFmh!u3vvxaS_N_6@pO~N8T5Ducpr^3Qkj3!s*^pI9E4< zo3%eYQklKG>K%MT^3Y~?GCG*mq0@pr=+fO0UB_O8#BPIB&4hH*BCy000XhGoQ|A`w zu*w+i8u6T5W<9k&k&GSd5O;SDRka` z4W0Hck&IQuF^s~QUps@`HutFq)e|=p_27?&PTF5+3R}Dlk5#~lCz4n zMtxb%2i9KfU7i6{AUTdJBqwR7JkHg~lfg>KOLk#S*hncDG?=qxs^nLVmAvwy@}!78 zu}2tvt`p?R7(01VHb?SXL`&fewUqRzl=2zX^6I6(yxYdvaq=4Z@bsNj({uRNF+i#! zSIDa|sq$)yR;n;iUT4J0JCkCmzGEOCiMP~{@2HuuTx#s^N)0tOpWkWa>n;QNv45=m zT4w{Dy^l~Ye7dY|rs{_1rYFfI&z2UsDf#eAMs9)~t-w!>@oC%9};!9JI~cR*)m1eU-e<`HMd zi7;mtt@+UHFu#!r^D|{IuQwE?RfRCQvjfJD!(iO|7|(;A3*)g*VVvd-6Gt7G^vQt9 zl%_B_lcK^L?N=@BoVJJKPuhLh-CE6o*^gD}oH7>nkJDhEJRSB2yTM_60=#~h{?~Cy6 z&Kb1JQ24%W1HZnbcy3B0+N_H~8~F*pz4y?1OB0@%w3gm?`tOgY(SN^&{QfGq_gf5i z=ihKYMW1|{0lY?sz$cKIOn)|n-=wZ+Lp-U?;Un;Su14!u#pLMXMR~1>D66UAubU<+ zlVnj%;q`3K+Umg?i=f6nhTfLN%)U!dOICFP^H5ev=JY6PKB(VX#5qjSoPEKNJXke- za$4p)#Ly?p*=#W9G+*|>lQ$*n{8rJHc}RAZAMrppdgw0jY~)hW?&bV9LMy6A|^Dup~sh)=bR{6(>N~%7mH^31lIo?QODGhU*Ik|MVL)LL6pN&m?=-*A+ZJZVn(bx zasz`4sL4!XCR!88QgYuLxq^G)D#?1IOU%+ha$>adcrnjuj`}K(+p+cz43(UQYb5(r zkYsZf&8abz+(6dVG>znSy)SuB93;O%4f*palGmV`dDP$KNka$8tyM_w?_zl}i8JSw z(NegBI+XU4q{1sy-u&5IJ|ujUnw0PI*=)IdtXLuMw|$j2tG7zk)!|arEk>#ecFUV? zPV%mQkW{bKk&lXF@-fR-K4xg;<8g(2YW+@X%BIPeIaB02=lZ%2C&?#7qn`I$=pH7Y z(7reH%|^h$eGm)-HlxY($!OLw2`&50fQ5bw*v)0mP{2C)Jw-7Fojd zM<19rc7<{EdbIrIf|fI8!z}?O{x9wsB}4jOWjW@!b-b zti1=*!x1n`=j?iF6D)f09NUZo#7dV@|5*e@z!O*`O@u|;53uN42Zbp;O$(H;cDxLm zG|r}_Lt$r3zT?zk*iLu_+lt|^H#`FempC};-G}4LL)66;!ukC{xM+I(Ka#FHuFCrR zBYRx#<}yWGgo-00By*g%nmaW$HFs+6)YRM)O$l*k=0sC7b89#vSElT7feTy^kR`H3 z1Vlyrz5D+DcwV3a_u}h0&pDs>I{4ryGhrqig&&_o9jvLBE4avfLgF+Z*zvsB0={$3 z&|^Om?fRcW+qIkdGjh>ZorShH=>e`Sq#w8*e%~^;aH1VLWjev1dK>>w=@}kxLdWR^ z%xIq|iM@zB1{15_{E+9V!93f!Nz#|2CHd%9YLsIn)xl3vGrBXsfX{q)-pAA|CVw+p zlIrUudHNpi!I~(>-@TZV97aqqlDdtp^v1B(LOFYGN|2Ovo&(Pk_lvC-Q*k-@E$-Lh zy<)meZAbq)_L6qgX=x?lF}(}T#8d|*NMg`LdKbD#GCgd`wXESmGsHM`pctp|+%{_z zHJp|_OR^5XA}&KrD7p83`XKnVCidG0tfBqv(bd)T#j!`;V*mY>7*C`xIdt;!+mbkw z_T#f=gCq`bAg{;Svv4W#*je-*5Uc&EkXJA>TUdY2*XXkdmsDycO}6y@F*iQ_-F-<< z=hct%56AdumeV?UBq;CnR%# zwPd;vXYSD%$*9j0^Mu**_{Sv4|80Uivr3h!pM0eDRRa6fP^qu?l9xLprKZDHsd5pi z==NXV8zB|0W2N#N?&4~DSDq&+q~=+Zyco4mUIYcmi*CGLFazS~RH-)~kj9Vv<;_16 z<=xdLSgc5dWt;Y}GEpN<{N3(+e>nX53QiF_pd4ob&8w|2)H@;zou5`6BdS`r~q_7!hJvC@%W(k=V-&-=dC(^0k4@msLN!ZTXzVq zW4?gl4Ch9t^KkJrK=*P1w6FR?yC59eN#8?D>_eLr16`mAx>M7j%b5k;lQYcM-ON8f zV-`gTbjKZ_XAZ8*h$k@YuYl|CTe#nd^)-RD_f8KF@4Il{e+6zQI>0sQH@N=l0=FUW z;C|^GJcCc6Wj`&vzoZv%oxodh7%k&B!8?#=z=j62+7rMU)WN40`IG&N;q!1Hd^k(` zl-*@rzC>%cMQCkv2|mSb;gfs{KEG{;4|oPX!#TE`KXYIVT4r2E%Zmf(GduwAh`-V5 z{b95|&>L+(?T>c%RcL>&JF}a1qy4FKXqWmK+P=KZS&Ps6D?BH)=DA3_RT3ZEAP&#@ zZW;0VxIU8N$1~Vx#2}rMB&{7kpMCOwI*sH;&U!0}G2ZMY#<5GNuVOD8XDKOR-6f@9 zhNMo}DyesxCAG9%(mDl7nvwWlIr(HgGh%P@928zD>AB5f+U>@Bx~CXdlGFZ~e8I1r zFE6m}p{FJ@lKQP0>aHz#K0HZ`qm=spaxs2;i9K{Q>uZ%5SMhyTHFK|QAI_efcmLK) z$|0?!b~i}sxNYkX2m$)W#%zpwfRy$G|2uk7P%PzNxU-{ZjN z%eK3cnn;ZHSx-rg;^+UYlQhn^rgiLbI}S>E{ZV@Exl^HomzWZ`vyx{-6MLv>L6Dfv zbM|cB#ecJxbU!|0hB!)k#3IRw37@^$Zcw)=UoWTfy4zag3V)Uf!_-#TQg zlw3O_RUdM%-MBtdmlY&+TkE9O|Fk?OpHO)=Un*MrNcq{rQsFsVD!a^*s<4Gp{g|~E zUney^yrqUGu9{ha@?t<2_P%yf_j9N;eA^(6F&*XYt4Mi&m-tH53RoQ+1Do$EVK@FG zIDEPfPC?8(3SRfrc7k z&BKk*(i5c3-T+9MQFn93XA?Ow)K)>iuxLo@ThN1^> zvnYeRQ$5@tO@sSCC2%V$ge!Am4Rim7OXFPbbtgU(bsg@9y2EpPIJ_3!hu8Hwcx?@Y z*SZLJ6;DRXNt2jk_zPNITFvVp@)k$Yvh#F)ohQ5oK80r~eN5YY;n`^;JQ~u7#qNSf z@?&^x*#wUkgW$gD5!^o<4|g{wxPLT~c+qaoxqra(FBMw0=ghq-1+BK)!N+hBJ{^Cg z*Wwyl4Kl%d|9MGRM9pMmkR(mNAW8k{x4wE-5?7M9>qZPBl=ECo0{t-CB=x0<+9~n} z>BKT$5SzCS7UM#myJoQW{Mo=>*N=1B1M*_Qk`h(N%-A=)S074ROiwX+-xAZE(~_PR zDH(Ol5eeH$uDF(;M@U8s-mBYB{;$Uv|F!3t(2`ipR!N@1ddT3JZudTFN!Y)}*h-3} zS&Rn{vetY!#}@LMFGf*PBYm5?uShX22q1QOOH%FW#hJfzy*Bb<_ywuGI$G*n zZb^M`ki7aMSl(3oNb~EvXt9I+ZuL#rO#BOW0i$5w90$h?FDUBUL3O^F`snj;O$|oN zo?oNwmz&YS7=^BnZMd^wH3B22Bk-?K1Qhu&=fsT8FU!$kH}@wfzhYltZ+ZL`JXhR< z`x@dRS9ZbB?iTbZJGl$&9JF=|ppG8{^`JkYK35CP@sFVm>knP0>(Fia8oHgAp!;GZ zbhg`|&5wrm-+!R}bu_d;t%tU%9=f#g&^Ly`;J*g0OFO{r+9lrm1#q8|3Aa9{Vfc;S z-61?P{$2xpWi1T0tUd0Yb8oAFNB5o7%(R2YQv*D^lAAwy3Z5?;sasnCFU6nK(QIU{ zS~W4(LvW8Ir}6w8+&*Ie+_VXX`ra^{dIiJskuU^&1eb?ZaFIRGQ#-D&t%Xb1WVr0q z!{8YVL*o&+xz2=pk1}d?sMopqFFa~f;8AlI9_9#m?EefN9^}gI5exJs9$(glJT%W) zPSjRXqm=klgd|n*o*Zp0DcgAu{}e_Y$3o@_@$&VT)MY&-}xC82Ve1-v3anh zdXz9jdNynBprrju?B~>cNsHS=ukB$;U+gCtZ9?cXP)NpwQIZ+dMa&j4Vm?VuAd~yM zejsM@AecMu?n?S1a_I{cV$xh<-Q`P$<6z0WQz}`mw&c|3ikW`K%&rOC{iUW(j62Xx zwUTEaCPgc}rL^d_RF-X$>Lc{Bj?I&**88O5g}+pEpDg8t%X!9Q?d=bj@(V#y(dDdE z_F5=ayO_0dg&AXe&q>XeYf8-^^EgPFV~f$^TVgBR|7|nH z6?Q?HurE9X$2H$W(cb{&p?qj=zk$o?HoR9mqSfFAw0ppP23Fix-kbWYfA8bNSwOcL z@6e??_fPuv#Rs!GqwN@HwECq7yiRYS_kjM{kcluv2f*dSbI?V{LpwVG>h~w0dhZEU z@@1%h{2H1_1GJBV*~_Lvcm5qSmX<;1(;3=7L!n)5f_5xtPA3+VoPZj8-@ z_W2NKmt{he*8-YpGnq|8zwpi@&}#NVdrk-4Bsb2(_Hgkw!KGw44D*^{IQSZd-2-9R zFa(A$D;Tz4fua9#iT8|>gaI{@@N^AzS>(>kOXxu$hfNGGNkP7B{TeZ*d6O%@FKO#7 z#WbIJ4k!LQ(-2Sh^B~4)F(n?j`H zFL9cmm13N%;PYSwF-!KwC%mdQiE$BoT^rUJ^NUjMospE&Qzd23R!KQWPVOoF1KuRW2+p0syt<2V80XcUv&fl`la$@}B{krzq*f3oSx&trG0C*s^su@`5-$mt z^w(o0BQ8cVw-Nu}kuBL;a&%XNC5Ia4T-9XBnM95*b1?ZidS0X08%Ob(Q{^VQ?sLaUW;gcyzn8H0)02La`^^XKmjY8aDH&-iWud#I!rmYigKDM1 zeV~-T_m&DprBt}&OL@Gvl=mUGkX|ljO-rQw$rY*W!(AoBmY(a*NKN<~sr@oaUPcd) zx~B)F!8b@6huoL9lOv_MZXsGsxCpCZpTTCtQP_22-CchIN2h8yZBfGcrwdRoQb1S6 zXT7-;Ufmn$%LzpL6YbDp`z-kD@1V28Jnm2CPJ{C0@cY^i?c1D2Yu;1d-Pto%PlfxJ zli<2aPhP+VdffwP4@`t6;6BtA`=FZa0@Y4_zRilfg%aB6@1YyV*IUsEx&ZdSTP4sA z>j14P8k)jM&_tv`bIBf>rs?c^x1k#t41GW1Eay_-+L^WI-Vv?^=`dVnopj*6&zoHw}+sq+wODrTu$llbw4Y|gD;D*29`iT}U zFT1d2JFxc_Lfi2j>x)<~u@hDNQK(v0l5-gjWv_NnPK$%``L9s@(HH6k+0b-$ho-^~ z+P&SNYr$v9M`_Tt`V86^$&RKy`-c;0I_ zn^*{YP@oU_y1Qarn=i&3BYo(s>(S-ZwhWhKo~e?vh@+JGiqV0+@jKR=6Z_TnZRFV2 zNXqB@zV`}Av3MvcGe$^CLmx@~E{>U*#7?5wJ5_PSI4kM@p>C}09x+nWVa&P2IWt^R z%J{y&<=Hedf|}@SlJ6OUYS#dDb{uO8>ntWjpm!KDV7zXiQQuu#c3d8KvBsH8({oWy6Z4 zthQJxsF$r|zFYN#p3GT^liCI3^xo408(1R^7r4KKSkT+|Bji2rp%#P3!^*V}Y!u^R zo9_VoVb|fXPz%Q^ccD1!0oBlc+>5>&2JVe>e@#qcC~?07-tZ*`>USxSxt7l8u&EM$ z9`(fGsUImL&R|>wujd!xvF0$`=B{DC_yYR#LpdLQ1kD#3sOkcsvKR&B{cTVU<9*8P zEzO}5(0)Gv+CtvnMd_?7ULCa1}u zTf(Jm84Ujr2ipB5Ty_^jKk^so1}8y#F(29uUqc)24{hCI=vIY5??Sxv;vKm36uA8L zJq$s6VYoCOu1+Sn_KbjQTb>F3JOUSUGW3U7v%XuQx#9x#*i@)IzJYUHDiqJQK%p`- zk8LxY&aUMy>ryz=H|zZT3+}Y`gDPwh)Vdh{ZedVww}N`r9jJS|LG`X0D$`@AGHs!X z34?0?afxfZB=L6hCGPc7iQ7rNNY`eGn`@T%ZW|y|3Xx4o%R36$ik#9ER#i;iZk)|5*6THb^I5ZAlL^Uc&! z=4Vlx=tb?uA!Z#6q(AK}@5xttBr`XZxkYazCH{aIHJk-^oFJx3T=jR>TL^2d2f2V( z)trCz%r;9PC&yWHD*IWxK%OZ_OG*=K=P2v6s9B8jIg9Q+D=B@bwRBrb|LQ<;-ciIW z*`L=YaV9+~#v!M9w%fq|$FG0Fy6s6l4t;qk)7r3yTXI(yIrLY-?04NHz2zqMy91Kh zk6hd@dnDJp7xQGVNO9gac{XjUJbT4V*>St1Fy2Syudnpf7NJZ{Ask*RKp69dQIk%CQx09s) zgBjBBJW5_~^pmD-ZRGv-V6^DXon6%ST2n7*J8~)PjugZG#56dhMZk%er*af^($pR5 z{|tlM>=y7^y#qefhPBPlfNyUlbKJ>!1xBKM<_)yn`!js99pK%Xc*3PvxCe*BHI(yR zcoTFt-#}xU1-09EP$pi4@&ijK2Ooh-_XE`QLTCbhf#%6sXgg1X)~Af~8?(*AZ$lGm z1I=zFG^?jWGi(4fJ^Mj3fVS+c$7&VPX&D`Ebl~J}&~< z8(Hg-oCPnjh7*dRZ8L#A_Xc#9eW9D!0Nt58^^H7!?fb+yhaEiIi9lAr|Fsm32+IDbJ`zBoLQK$5+!lREhO%D>WghG zB>o8fuCu7^-y21*9q(DE97%7>n#<@TS-tEft4oBKU3uS(3=>n!Tf{Ik#OO1CJFEPd zyT`dRHbPQ=JSnD7@>$EbNoK`iG3QK_tVstYi#STwZfX*F9?V{MMY0?8l07P4%&mEj z%8!zi$7k5QsOClh-JpJM|Ry$Ehc}5 zs}DH8vfsK~5o7iNNge1VX*aZDqMjwaH#xW4!zC-1x%3+=rHI*SrJo*cS-Iq#Nnjp3pC^wGO18~i<`z+x65t`3Rh%uibN0MMT(pwhLI-Bwd)<}C zvzV8e*h})AZ%aXWh!hRIz`a}DYgS45-3%%3ahH4We5LH# zDk-bytjYhMifb37>YJ1DJT!;=Uy{6Bcv$M&50+P7+sm7N-R0f**|1Q%pv9Q(u>4~e ztUufVn~R%ao7o+9#tb+(C&RId8Dq=8g8GZC(5-6;R~d)?XzFdY2}`n&6(jrkbr2UDPI*aPRJS~#!o4CjV= zD66lr|DA{W-+j>ZkZI4+$V3M=XD3#?zf;_R1Ym@0ZsS-s1LYvNA*BBM|!~N`7Agp^st|I zoA~c}*g1EB-DWN9_u9bGW-6REbcN!#U2q=C`kMDEoZD`P;`}fud+B+XDxA8GbFBwdXWv4)FuaVuWFto`e!f`jN0gF#27Pq?|vB|=~J(X z+1JE+VLnga2a>f`A(?-(7mRF_R3pz=NxR6UWe|rvEXMj$;_w3{y|cGu4&Bcl7b;o5 z8Ms$*6ZKNvB=DemYmm&X!^QN|KuO8fi_w0UB-I{Z_A@z-ZgYvX zPUa3-a*So<8voiVN!}01*{u`f-6iZxb0syK8m>FD$#t{OotVqI-AX^aFY9guwP+cV z8rs0Kn^sc(+(WPKa7h_?hJ0NYN%`^sJxt_F5?HfSR#E>=jFY@{(ku3EvxlT?AU^uS zAZds8QDhd*8>%Jm?=UI;{(zMIK`qz!1DWL&DU~60@+@YF6n;KI@(eZd__awM z58!j57ZJ#Ny>%9*4dw5@b z7X?LX9aPJnK)Y}zTpqur*3utd&Fj$Wc_7++%)YmqIbt_nqYXd$Y+;7j2c4InDa zw~23Tfy>1u(3yBor*X>!{9xIRZtaClocK;C$gSl=nEh-N=EO8O-Vj6QSk~RE;m| zYxsw(xlf=u$eHreA!v>oSd-kNGI9bm>Q(&OG^lU3g2r_#G%;hDab4fNf&V&+9u1XS)eP&&szv2QLM|N0#EkA8z)+aFe z{9M*tJTZ3X36fN&mgIHh!|t+oc-$ptFkMn}A4vL?o{~wwP}ZY~l6_%>WKW#Sy&E&w z&)P^@9`Dg#i^bTRXD*)oQ@T<2+?!a$F>+SK!OhQvIg$M&Yx7vinx4%ai|iYHZ*vc$ zg=Dp&Zqbc%Yljh%_HT%!>}#TClXZ8T-hxVlB<8WtFX(1_roHbvNr=Sie<TZe?6ef$Tp=a%=qYdIBW2`T%gfG6 zMaV>{*grrj7TMEhULq9-e5BH1v{c?Aw$kh^&+i|U7w2clOZs0MJcr8b=_jP=oewND zzoUi!_h>;b$}&EiI!!CsShj+#qBHE=hr@pDO*oE7fnvi9-tBJ^W8PbudNHWaF98Bkk}=PdUr)c1*<_{4CoWS{$s{U*-~nyfFO`C}9`bFM+t zaxBzQ!=YZ!KD2HJ)Q|YyK3&Vc#91`9oHfcCAAN<~<7%h_lAyZ7v*b+HQs7#s`fP=2 z-f5^pd2T&+iqC<5P~E-C=L$8ce}%yLr3;*QoQCuHJ3PZ3gVMSOoXvG`o@0Vyr$3wm z&2ZS+5q3ElunneX_^3T>0!m>sRs~!7+3gl`M$fc^!~1t|y#5}J#e?7&+7k{b%#CXL z8}@S=s6RggyO?I!4cX0Ho4&AZ-$r6yaR=Aa5Q)#PWWLY=`a?rGZ$^97n z^BH}gKM_X{Dwj+YN>(R&Ru(r&_WC->8cdCEYhowIR**aBLmi2S7`rT#l-_3~ZPZrc zFcH*f_=(vrNHUv~B=hv_{LhZ~~Jhv?wD=D@7y+Sv!kCCSvOHTZBzND=RrY69e*ex;K4be0P&rN%d-ZkwKo0wwHqT zfy^RWDmi7=^0jLma@nRQZe0@Gwpt<{Otm{yP?#qgh}PG15%~CB~>>9 zr8?SHYDR98+IAsQ*Jr6TG_8|28_!B}=PR)IWj$IPae(F6aj@k$rE1_|BfbL)7D=V3k!VGiI@2Pt}L`{kdcQhZ&N2?A2JlDO3 z*FUe|*`f$;Wfd^o90&c?InYLO#ysx~<@sZ9Zg~%i8MaWYz6Ixi-B9|CfwH+LRN>!1 z?dcA6_-ttW#<9;4n`gGQ)@naA+&iKB;o7bi@Q1xJKJG6nyHUz3xOQGt{oe%?fjU$iI^=~Nm{|V<^ zGoWDZib7Egg+mh*T^B(Sm=1;K0XSV3)=*zK+-(7SW}?^);_m9{TG&Joj~bB!n}IiA z6Bh#8b4y@H9@73h`u81U;V|kOIJE4|U7vNZyEzng9Tvkj{WfgZn3?1D5H^cDz`D9w zq9YDU?AmNe7_;<$Ip!%V?1`gSOInJLm;&m=)P*(ox(&JS>C^!aB^SWnG?W;@W%jCS z=Jqr-OJ;k{V4Uw#XH|>w0I`m?#9?$iQ`ITO^mLVExKOXrhM4B^5;4V46MZ31(*4ho zN8c(LKK%RWZDKk-SJKqiB;|)lF@DNk>pq|QnNUgOeyPOK)OKy|$DIjFIroN1(&KZ? zSs~U+zg^N@_P$zja?6P4tXNY?~)(ZRlaG zJuRl$dTP}ciYX^fOw-3nnv!2Xzd?-NUXuJ1`{}7SlKd}m(+k8_AKemT{u)Um&YE!| zm^lsi6fB-2xHbaV?^63e>&G~f(v6KX< zv>Pi`3)e_hA$4Q-nPqf3N2>U~s%KhC^&68sFRGOnU76WlFkk8|8{}2?9(g<1EX`LY zz~awpw75J1mRr7n)tYEnpS=p3s!Z5*t!J-03x^WsX|8(!=Ro3*R=m#}_d#d>FI?RB z!L?Hm+((^;=jS7tcgY;guQxM;k2wwh<@5bPKCJg(>YzV>Ve>ZVPhNrc`e3MUu(y>c z;2hQuii#;vH2eeSZL6T{;|S%G15nK&zW1yn)NA6PvD*vHqWRF=;7n<53r#fV&CNI1 zGoM1evMW^O&QKj^9sPb8s#JdO+g?zsjzjIuI`laJHQ%o)gBsHa15`f~JDk@9Ro}5t zb@>XakIqBY?=PsvlLr{K8Oq0(;ry%uic|J*+6p*kK7`|$r^It3;CS~tIR176jvYUQ z19xiL&;15=r-#5!YYE#kD`DHS9yVoq*re)VtNQ}BD-Odp^#yYgKZV`;U9h`FFF$qH zc5Rqp6PgU$mOsNLaW8l8`osE(z*D7kU0N4_MH2J z$@$UWy30~bYI5lR@eP5iBn7)9yRzReZ(z8S#zhU z70vWz&G|{9H|Nc+4U%|5!JL*=%$x9~uXU3oC7+cfui@0T1xsQBzh`%ZBu^Sne1~<% z?@d{CLQ=|xbI$aUlqdTnwbwRDn@PP|3N>EEULrrdQMW;j*|4RxX)nENy+IDB~Mbkq;RdT6u#Lc1yeYG zW^<3vBkng?x%B_ft<26g_h8Ls64!L(b84WOf8HbJPl)R_ZY8d78{z=$zS7-m|AvhToFvd28hPM~me7AkLH^SH7}Ao|k4z z%^ah=@Ea$!;wUd4g-df&=Bb9`<0x8aPIc z=JVzl9D-ZIf%;8{!^8NwE9~p$!oG_$?DkHB-DuX*uwvK^%!Zw755}79gl+8SywtG0 zTmswG8(=#ulRGE|z;@g#*mksoP3&9PEacwL!0oW++1PsDY*@EgLcKygET0^as6Qr4 z>}>j7(=Dk#9>r{f*^<`KMbgzG=^^9*z8XQjMU0p%y0At#XRQd8^nJv@w^3*L=mxnw zTS?b+q=tm>0XdRd7FCe`QB3(FaB z6nTJ4#O}us*AE*^PwQFw9H^W8oL<;{)NHGmvyx5!!0Vp$){T;+kH@jku_kOSxI4>> zS!1k6{VwX92Qo8kx+E1)hf;q-jCKB!lG-GxOHN4YZ=4}xA|-7pahvBx$%q^)=8nN) zW`07JEuS4d+eyX__U7?NCAH5RVl|hjljiwu1Yhqyah>J+SmS15q!Y+hP?Py_sAQa} zW%gwo$yx3uc}MTc6T5Yin_(+Cs#)BHpqH%3UEBq9ls#^MWGXphF1a9P;xAdfiRljK z8QCzExGwQ2)^|=Q_n5ulF2Rotk{|3Z1y_ot@JD+onnrK=j|wSqq0iNl`jnY^sj{KZ zHQ<)iEIKGP#XQI9S!;tv$cx}U^5SoU)P6EbUU~;g-L~%1utcQs?g@F@#z)?LJyzcT zKs+-i2$s6Guv(i78?Pm>tse^e0_JGmiG*T$6r7{|$ps&Ts)$@v!YgRvS$l6&p`Y&v zgVS2La)xyGoI`yguR(Xt!ck zJ@YoK9l85@Lp*DabvM*kqHXldr+ve{bG_JSCep)7jme-jV){HvOgD#$X(zQ_FKnS)ASOshURPeIrY{X z2GGwwTjFjHmAF#wE>O_}x4xYF@k*Gz!kQm4f;dSvd4m;_w2Nmq8+wvHBX@tHBQwHs zB;igm^DkLrd8~mU=Ok%(FM0&Sn0d5K5~62w*8=PG7yjF^Le7Vc+xL!skWIbV>e$ze{!lbDd5| zQot4d4z~0n@f`X1E_?VQNuN|o-3n``Kp~HRzbiRy1LW}n@^Aq&$QJ~Od2Fp@PUmxJ zf1qUSoGh6eZ*vFb3GO!UF4-D?$ucgNY2X$`72J?=-%V24NKMNA+w>mXmfD^PQhQ}OcOuZg@Yh_aT^%U3KNC+G zu}SK#1xmyAp3*q0T;8}Hmbb-iNqg>!IGwuIsAGU^Za!o6i5xHF^2eeN;1#eM|W#1CQk zgF9q@I0*g3An2_4y~nYu3$@>NsMb3{`Fas&*&wKf zctQ1&y3e!hv-}-3ThB>d%lKq#1JUj^yW-?6b@kn zVgLRP?4NxK`$~7%N8W?|v|F(ETLAlB&amG}O;6RA)Q^Y5;j0ojjI`zc+)3mSR>SUI zf7nJ*>sq*x`G_CG`o0#{eVM0`Q3R{&yLgAx ziX0{?#qKbb3gLMoV&*J%r%l4s~5v1b#XH}ykbe=eUri*=#)fTNm=R5{0H*X zep@;Fv6sDXl*CD#3w05a=(t1@eTGV+!zyO&MM%6?jKqezNlciLIi1{dxwDJJCG3&- z>QYIFqBfKojl^B~Jj1mkX1Ru1ZT<`kdUdExPWWJ{Bz#M+T)}=xWWHJAGJc%wF9~n$ zB_W*uesmFYm`XX9at=LlSCYpJB(8dxbLJ)PO&Lh6^|qKEAC(Nl4A$2!$%4M=_o0)+Na1|Bh@Ol&7VM$C8d!UU#7M`n?w+%^f1?&t+Ahx5 zB4+AMvl6IZnR-&P^~7t(R#JPJEt%&zzvj&+S2sa2ozIGS6#2?~3TBC2lkC{BJRep| zb~1hDn_08h$vb>|gPi$NDYP9bh5zi6B1dm2y53KUGpeP;p1l0T+fp`nn^cZ0mFHt3 z<;4jfdFgaOUdF6oPHninBnDL*8qOKBS!#>wr0(lo(%^qZUL9K~jaU5S_1g#X_HC*( zpJMGr&xYj$K97B)VLN;!?Ee@HC#MT=?#BJ+an{8BBB+VtxhV4*`wYDU{r{$B@=NH) zkAuO&0N1s{;r0`Ah}!)?J$@40?*9N+_7OwgPjI>C0X_He=tixE)}A=XQ{wf0oDW}q zLEPRR>fO;$XS4pQ_~#f);v*}dK0SrlNCfpOW1#gP0WEh!YxDjlCc2hfcLKEg{h-yb zmSP`3bM;&H!k?k8oB(wn_CVt!_U2Tm=XQps6|XevD8JzQUA_w1$<&th4u-bdD`?$V zkMzr{<9T*HLfwr2cqmS`fK$)4a2z}p4%7fR{OJaVkZW-0T>}RN`AkoLI818d3_ceQ z&2Hq$v*EbtcQ}4E5{@md!r{w8*w6f$IOQeSe%uV3ul!*B8=q(PGhy}1U|6y5TZJXF z{#wH7BCln?!OH1hSngaQF~MOH7p#)LYIV zUT@(=pUq^>jGPA(4sy2+vBfW0D_b5)!XKs7c9M_yjv6z)l3e@%iOX5U-quIrj%<+F z)AuE2>Nbg9Hd>-N%SE4C#n-;Z`eXL{xw8^~g1)(T;2PAQTT9RZa^OR;uat8}Ze%MBg6+Cm! zxFKmp%x@lkSTf>c#2kEEvge+rOO+6@wQoB zg!Gcyz8UheVwTijGD-uzT(9Pwmd5GT@`n6n(?1X8{c$x~%sB$9L1D1jkPo|ZcQ{U@ zM(j`p^(36(+D?ON&>mtD> zZb3dT;z#OOsI$J$?@yuc=pg%U?--snb>!K}n-@kx<3{d6y9LVIJZ}#5fWj~wP7B_{ zspG$JY^;al$;EIS6avStUEw&oI~=zs!7;Km9AD9+7}OR{^hG)8gZOp%aClB`&hkgt zm5zn&qd?fC?1GJIAZN=Xuy)!At9}94N-cA_@Yhi?V7K zb)6R^uJs9t{WD2odvJ$PHuo*mZ<5%_+}(WWti<{)k(j>a5Ax%^g(O@Hpy!=_^QsVLn;9j+s-wiG-j(=2nKAYU>pJ<8B(%KGnq%JI zlLqF99h8JX^8AZyB>`rm%h!6jP;v)1 zOKyOhtFM;a>EsUFxnFWnspN0GCIuf%l!D%uq+stpDLhFIFUOI4e?p0so|KX| z7o=?6D5>1lQJydNmKPCq^3rONy!4`9FCau-cqd4W`JB8cn=UWuN2ourTpHGokcOge z^6J1SX>2|tZ*3>YyN&*^7}&&lrvlbnVqn|+7!Hdb!|9|GoCnuI`4RP8>;HuE)CQ

bRwC)Ric?NXG z9B8ZEp`AkgrQVsk5Mn1~C!xtKqV};5G(2N!CVvA>@I`2CdeisQm42X+(Dxv3X!eCm zN1hk$?y`SQgI+`Z)|K_pF&|Io+Y{O-dK5NOU$%?&*gOfk-+8VayO*!K2ri#xz-7oA z`r6l03#Nf?&q~%_7ifF3PJcF#e?LWhhFEJ~YG?+MOGwLt;`h~1e0>~FwO8S^{~tK5 zIS;2zRdCwYiGO|wr+eH{T{I9*4@=;*kX(6VAL_<-!{O)Su-{KkeLeA&{X8?5O{A~! zOW1Te0UKp2*f>6hjYbU{2ll}Np7VkuVa0P>Y!vgMdnYk>k{sC(&T2^>lK3i+o>09c zO_(Z4iF#)F5yN=dM6DLjRh>CUo@G8nK>~XXdyxb8mMDiy?6@})ll4$ymU>Bad9_6E ztdnRn_ne&VBC(6PKXyZh(Ukb{pzd)}6)@7^${ntQ(59hR5_p%V9@hr}N`APKuC zNJ1)m*`5oM;1a}|^ON|~Mv4DsxWtb>$?W$)N$7D}5|%bff~KFuKcG*2xJeR2c{aQ- zTavre7c}|`^FGPFtuXPt`i8yJ9^#{xG%(-I%QOnPdh4U z)!T@{juxZwz9b)J-CZ>BZ1+HtzP(MJVmW63@_ZrmydEAXnLGZ=PYII8?+-|Bb+F_S zQ^`BuQF4v!VL9Q<5MyrUmTr>UdA8)f&XBwl5#$|a$dfMIGg41}?>Kwh{Be>O#yvkC z-twgAFf%f9B!3I%&96!%Kl+9gyn84`A9AmGHFco-6R6j0BhSp#X)a`*kvF}r!BO&} z4Rcs_Z|)Qa5>)G_+YIudGkVtC_x zu6@3vc9Xe({`29!HXH5>XTja!3EVzsM%ZWc>G-~eOJ_j8oEZ-9Cqj4Q2y1Ex=g))C z>Aj#$en8#6nc6J!b=%k*O9n%?fwOEEZ|I9Rz-9SHxSV(3dGRy2bf>Q&w;%N35zsGt z3H`8*(6@DmUO52zuB@-0gZNrY;gUQXhM68P{7qkaWC;wPGcRc~{jwod(EY)g)rq?5 zZS)`IY=k;>7F3bMW6CX{qz+Wsp%a|%0L}w$lJoc$in~5g?2m@x+h8cZIS<9Ep-?PX z0fq7)oOa)ZlWz|={$mBlZX4i0Er9(JVl7vD&{vg2tm+zUbNyhOw->f0dh@*a_`XC>UM|sXu1WNTMu~Y$oweguiCeKj z;@r7wsEf74D54~~<3fpgeo&$hca)gg`x3XZi^S8nl5ogE60WwBgg(K1jnfkUMWn>L zZj!j?8zkN@PvR?0l3+GVqJJE7Vd!BR(p?hHAC|=96D4UrdtOX}B)v8;hhixC`%UEU z_e<(J@^!b!K{hUwjM3qed3uAGR};&hX_CxobIBi4^X*0q=6FAPWM_$S`&jNoCyq%h zCTUT3_R(9C{5Q|D=38P~vPLqVQu8*ipX978mVBk2wQ*32iK7v33F_ zzhgJVX=E;Kt>hb+S^JE& zx1?GMPLjj>^S%`EY*$p9B*m=Pl68Sn`faI{pW#`r=$uqv*d@<*?UU!X*#ln;l$zIm zQrk#9^sqX4<+?-~4a~0%J1C7&i+!hD!#&KKEA` zRLnkF-3zX^jr7iCQ(v=ze)P9+JrW8-S~|UO%;a_Tq>h>#hoLoe+xA0y`8_nrOQ6yI z3iaaoJkNg3`I;HN2g9HY3Z{-_44f~13g>kiI4@ld=lRia_8tMnx)aoaO{5 zBP2bG-qb7Mk~V^x91UxDE{1+o_hv zL%0tza=tv((TD0ZTdKO&NY(LbsraW{N@IshiQ9ZBUSO7@BZpaw1Ei>Tj1+Ztl)@(^ zQrIg>3YO>og+!aHM2HI{*HVpxVT9Qk8sDNbd;j>NGbk} zI}z@9$TP1-DK(j;{BQbW7Y>lB^FyUNVu?I|vtM4MGf%9Qn>384F4J+jyyo-o^|f{K z`oRr(y@A>7le)<3reb;fROEd?Ia=Jk&a=o5ukA`wL z@5$ots2ic@bLDyHZtjGO<^l}gTf+64C)^$kgZo(%JZ9{H$E93)UYTohi}@#SI>K#+ z8}XJ4aNSP4GoULBFX`F7@dXiDJnW)fU#lrT8YfMFD~SPmt!W;w5t zcQYLP2ZmECV2D@4)iMjNgP4=F_W@kln_Mpqf$PQD-1 z`7VX!aGSWB?=u6gUi82I5eq}#A#i!PnOQHFp`U&exKYkw16^mJ(QO#p=>vgbLnH&-dSo|=nHc11w~70$*SkWY1chCNerC4O5x@j~VBESWBE2D|kSgwO1r==|YKX(Il}ULnW3NVN7xtiMg92G1QX9s5v_>3X$mX z57_?{5?jbQk$#?p&2FrxE3Bu968GD5=2I_`Sk`jfLSmhvb&{~YyCiD&u!g+(wZvKa z@#p8THY>6vX3GYNNwbzX+iHo=B9Axk5;4XIVjkX-IC3s|J>s7aw@IRr^X3C$GCAZY zMsrs5B#%)?k55Vi^_b+m?`ZNRQd%->el^|{&@m@<+CLxH$)0+CrJ6!6Y^r;U}?B`R~m=jmWKH! zq_%yjR0*}4-KR^*F9YQ1jHU9_(T|>?c2d0Nq!h)FXMd`dg1ijLFJ;YroFGrK<5@u@8ow)aIMwnDP zCrM4vK&icVRO-7p%Bv1%r14~iyyl$prpJ6{iZyUg$pz-vddr($CVAVmPMRyGqQ$Tv zSoI+m*Ns}Jwi-CN=fg3bbJnMG=u6CNyq;H^~38>ub2$gO)n^~&ZM`8 zdbhbBz<3Pj_!8DvJnv_dpCsrrB*EKW;`g$jr7I-v0PlY-_t#ydA1{VIX%>52;$VsEPmK$o zyRp(nVwLHR2)^(Dq)8^N>R zI7#fpx@*_Wk7t<=Mhw(kP7WfHTmpId;uvbELnO(}d2uuG|DGO_;x<82KO8P;Ls@Tk z$-(tHD5=_8)MIuhS5F+*svCL72xg8oQWxDI$wPVd4UpuZarE;N*Y&#~X|3r6+D@%y zOt9n@o{^_>sL%SRyEJyMmiMd8XfdJ@7C)?$x2Kq8xv`&ARgI-bE?Y|IA1LY6Q=XQO zmZuT?*xyHr$%Pd89p)^_v)tF=^5hovWfu&R?{-)6pVmr2{wOI-BcE`jnSC==p7z)$ zB{sYY&hd=5LQ0?JOWAXga`G;fN^hwuFP56`jpP%SQ>Vg=_G8PXF^jeK>jZKLtThWa zdHo_%-UKzs+g6p*WWFNrziNXPzNxUhrHA!Gatf1o!#;E>91l{XIi>@g|E0!QlLGaB z_#wRs+7oS{w=}?I!vYvm$R}ik!_EATyA2ZIIbt%r{6B#g>&~lhD|o*4geQ0Acn*BY zT#@gY)6Oh~0h8e2)x^DcNpNpq9t7u0j{pNa5Co5oTDU*E2=`s&Cb~y4tGzGWO2}1w z5DfRdr}(wx>3>-X&kvj6dG#JVGq`Kv2OT`}e}uLs) z+06H|@(FhiS-|VjJ9zCo4zJGV;dz3)!k!oNy)gH|^>?^twuj-T>u}k^T$bzcJUgnO zd*BHjpDnt%#8*FaftDPlrYxA}Liz_QZa@{%2-OAFru9cq%X+AXkdOSNCp`__pnA&d zK6~b$o1nT*On7}SD35J`vm<9zpY3p193%CkW@r&Pyb20{QI# z&V`BGv$JWP#7A9__`_O>Z!HqHT`95YS0r{wIQLZ6No;n5#BJn0*aQ6Yd-gHkUJ~;w zuc&k6?AX&TbC%6(;;c$cahIM_wPB$V@MK1wQnbXK@>(WGwsMs|+cjpT30s=II@?6wkjQh2f!6;4(w9wYu?hE_AYlGby7Yd`MtNkz-==Avb%A@KTpAM23+{)}~SzXF~Sb9r$`$ph{? zui+j%e`-93Oof*(>)}>=c(xDaPG;^g8MlO)W+UKHu$b8q%qU}?p4Z)>Xt}s6TE2Y3 zJ(sy?d3qYW3N+licZ0cKlUe&C;JG0NUiz_UsrVhPBo*>{W@J>7ab$&bNp|)QovpLi8FAK`**j@{MxWA>N>WL7B}tOs^ZWaw zM|UK5_qdnW>;0UOFes!(EwKai+w<-0oD97ae7l{UPE3V3M#u_LulvmR6Y))Z;vkc( zn3=vpl+Vq1a~@^q2lF7lALQ=i9O^R^e(W>?z5%snmPjgOMZB3{5duM?r8OMI@)xyVQx@Ew5vRT4y}6g zF6<7&A7judxBy1g1~9>&Fts#cZ)65LO4z+je8jZ#RG9pI8zw#3FWJfkCY}1i#F-g- zGd{uOh7(L1v1hU~bM#)u!bI3LyLlCieodjD&l$!hjbN<23X{EBFrDB4GYb!xy{mwE zxGT&z&w;sVJj})oU|t?On{T_pWZVgutlPj$%XTo^&Rm$^Ww4me?CZ@wu=3S~)wcg& zWwI8QRu|FvV?NB46PL_C51* z3wQ^oPCy5}fBF8TKIcLZ_hcw>Q)jfxe}i^`)6q8f3EFzqpv`q>wCPbnPH`hSl7ZBp zK10i%ztCcNE}9#(B#u(g**ha@dbZTDahwZ%_9Lf>@~NIElVe5Im3QXeW1JcOe7j`o zOZXO?p~aoYT~^3WlWN|JSIR}Ti!=GFxu^>HIb0)3YG0K7$sy!%-)TFG@;vX$39jVe z9S6nlE=s8LCc)*|-EH`G2wC?)>v8yR>rZmCedC>3+Kz*Q)%+H@`98# z50$dnyjOif=yPRmZB?OsY8@}18xTt=DwHqBjitP!Ncn*qQn73(wJHahnOQ5<-RVW} zT_@kmn@dgDZuv1SM1DH$lwT)Hpw(z08W3mDHq1lA=A)t0auXT_&p=~DqUm~i5@I-a zyM!4&)J{&NUUE9MC*Gr&YxxFklbDZXm5vTZYJ6iD{oVtU2p5^>_&uI* zen+v3dFpYPR0P8G1-~Y>1bLhQ< zozI-9=*#TxvVv7=F|4P}M%N?_yU%rC>(&`Iu_w@VZxpP%bw`&*-C)&kEG+L0gynp8 z-1_fh-_Q*QpF3xDK0-ZkhM<@GI zbOi6>5qyhIYs_q!ztJxG7TUgIu98(b+6*FpsJ9db`-z#Fr=rFCTcUn>l=G%7>g;o( zK9eo#)>B0F=X+6^yNhbj3huh;#3DH>hVxc4YhR!!Q^o07Y%Dn}Hxlb8)LhI7YE zvaXz`f%)Y1szkN4lPKeMi;`HLaxA|_!&}qYfOyGeQ4Jl$Io6XDV*HvTO^9KHOX?Qx zrQm4xQ(lym7Q`-(Zj@vX8%d1rBnh9ca`r+caW}cY4xW-U_?#s9q)XC&$&&oPx1z^5 z-m(03Hr#u4oVn4wH76exO~^;l+#*J@T_b5{=)s#~&RpFl%%^4^V4@wp69(*mD3R1# zC#WqQBx&p8=$9}hUr-?Fv)#%2xzPVauMl4U5C_B=TP6|+;Sr^x%u)ImQcZnMTp-ftc%A4VF=$9>iEsg&78uZ`u) z^vEd5njSWL{pOetndybZS=uORbL#q_(NX|1Oi@CM! zjF}@e`y4uq*MnhOAL54p>)7&vu_^m`5)|~orojAlH9BXWN9S*i=#hN{^Q*gI-pCtf z`v<^mN-4jlIn0vh!+gRjc7CPvV>rwQH-gz31xz)OFx^KF`!`pZ74Agm#_= z_OKe=n)wndU~QKG>n#pQT$X^L)h>|pJ^F-*?%>R!rz)h#;1-jhe7QA)*>i+_O1%qIWv8A~;vu4Mq9#7J0c6 zQSQhl4mzH1!*o%8x1fIP4&RL2tyybCLmW=C-<>)cYP`l!Kb^^2_do91!rhWwu~3r6 z`bwf>og`e|E(wWNl4xX4&V9HfW|c@%xSu2k4dK0eS(K{7#5&)LI_4-foa7o06KAOp zp>O;%HO}XlFWpw2b>zH#=APTqnmU{_l4i^s^CjPaZ~60EhI1zA8C*>Kq`?Ep*w#-n z{pbsKv68H4=2c9%Dw#<^l9^8Z^bb$w__dY%L*xYpcapNwd-6Gdl~hu{Tv6&v?Or;|;{tRLG*d86T|ALXV z8H~^MW9Q>gm^(LvMbmSz^mT^ie=+?00TxGxu(O1F$g+<6=q}8|qBv_g=sbx%AxW#z znY*vEdmEUqcn7nub78J^3+4&tu;|nkmU~*l>g^m@n+K!olzp(-`4P6;9bjkQ6?Wsh z!On=exyM$+)}GxWE~{bJxG%aL9*FKI?xDw>KhX0^AM_e|1J>IW^gSr6S5gvZS7A3` zGHjjeVADti>s#UI;^ha+i(SzCE5(*9`k-6s(qZPQyNjZ@*X@rPL%H2qBP^4>dTp#&V90$yDM_1DA(AF(vRFh zdYLHn&Pei_PLj-9O>s1b-C4(|{!u}!QbTyu~F zWhY7epWaMYoBUvzB){D&iko{y8F-r6Xgfus0+eES)rbxC{vSeG*hZt@n znGxjP$-!l+s5#rmT$IgQ<>j6;Qe+Ux{Gn=jTWBC}qv=!q93#bfgXHz^NGUv(Ltc(r zlqvc2p;LePV4S?JRZCGosT8j)BFEq=Z+B?RyKuhSjDqFk3kP!H2kBq+lP_P@^2N2e zeEwTsK40fwZw;g#MI#lPsLc#DV17!SR1MUVs+2^j(v(QmTwkd&R7;f!^(x2B<=cW# zslF2@LJ#NIdgy+n-u~!R>V0paIp13?qUc#` zN?8|OQ zuZys{{{dZmuAvKO%_=GqmMLpsQJu_u7Jts*X;`#70*j07VQHQNOQX54IPwCWOViMq zd|c;bcUY92h1Iz>u->E#o6eQ6-Ek6jpMJx>Ul(+r_X-aF3OGD{42SFsaJab#4j0qd z!F(BxJp$p>=mwmpw17)V4cuG=?&Y`9SCfUlv5nyV`6=AKO@mwiWpLB(4%hGV;7ZN2 zoB1ucjoS~m@da>A{e?cUiExPuLGO8M;2hE%y_ToI>EB%R{I?vA!`KIO@(JuU=FAWs z4jXzHtP|5=b+{!gR&9cL1bz4^>`m~?J$!r3B{xl8 zF4$a@yweoaXepNL7KKfnB-iRm@|RLc{{27{>^)VC(TKw1gd|@izMthTNz=3>DczM{ zrxB$W_nAHS+&b>aFz!fZ94lYOi}K-7-kY4WryDtgzM@1qah40BoNFyA+Z<8d=bNy2 zjHvY&iuwZiaAp{!?A$NvGVa@Bg`#ZE-!FW-Bsp>RB10r`(R}J$$jQIjF3B|;Me(+P zZ%r5eUK>T#X|Jfp^X9zjF6#bOlG3k?cWj`f)=_5~OAh@!wJ64sk{YE?et>T}9Ruph z$R)JdD$kbBWruBnWbB|`tm2?#?$ws8jn5i$%taE^__6~_)_M!+GnS{ zjwJs_tfr(WP~MJWcFK&o%)qRbPx{oIbc^Q=c~L4_>Ph+Xjl3!8{aMd^zMN{_v^r9; zmhZT~YUJz5CQ@}QPQH1Gd#+3+1OPkRW96MbQ|br!5!G={b2JgoECrMK`4 zx+Fh@RoyyRxle-S3wKzxoB^w|YtW^KAG!?e1gn<=V8x6;%XiMO8pR&vb05+5{%qLR zG~upcKgz+;=+XWd93u{)XMPc!D$>yFvOS!;jD+)FztCG}7J6q+M<2KCoS$##XEX={ zy7U9+wHuhY5Q9z|VbB%>41BDB=a#OpC`;Dz|oAdyEj;PUllM21SzOGm8;Sl&6-QL*1c2gg8^*2Hn zEQDof89Lwp1#{a7n5NBv344)@&u74BFMGsRlM`!QAPVE1qOc%F@;_a37xHi4v?Mu- zJa|W?BweH4Yw1==ijI_|-<+{dcO;28XyQk6N&Juc6o2l#ZTvnDxfj=%5{D$7@|b%q zW(wbQVcbD}qG}MtEZlpdoJF1Hk3vyoSu;=Ruqf{nbE%IJ<=Ys}DeqmY)AZ>Pm-IYC z?MbGjG&bb^@+Wt|f1W#>^SWwDdUH&Y1A|1-pMOq>nz+w0Q9UOvaribQq;e_MdMQ?sdv*Pjk`L{pLpqhz9AJuL}PnLQc73QzwR%o$I7U^bdmI3?Der^ z*6y4d$!yIX*OYvnI!Ce+@+I>XeY3}#OZs(cqlq(SY|@de(YxjOP;F+V(TDelK0YS{ z$(_4Gavx8TyjA9se_^N;*bbFfr^==9)e?EVlQ-w)9C_XTjJ$5lUe|W}rKBg{cBdkx z>|#E-fin3xn%!EV^-``)z2(htDL>Bca+J!KPt0&g9V6w!HtHb0_&W&so0xWknvLtyB-+DAljeNll}0sqJ1TzXC2pD^Q;~4_eS(b&{I+3DD`a z42_;r(|h^{n(n%TW|PLEMZ>Ak-*}RFTusnwyaKJS{-8Ht0X1IJs3W2Gu0Gi>{xyv8(GWtdGXR z`j0oTK0KCxKM-Au+QP+~G%MuzCyc^vxnh(rIdUl?Hd7TA3VgA%@C=)k!cF+LPNTi(O> zawL4;8^L#(J$$~X;lo@)-xm7tjkp8fckj;VGhr^AJ$=!0d_5fE_rgA^9(KEC!G?Y)>mTg5 zWPhclCJ+{TPoVR>a+n*t!Sv_{Nj^sJfGc-LGwLS0UZt*?eD@ySZ?nl&)4P(?i^Ha_34>-1ig3mjG(I_=a=J7v&j!;xivb z<$szQOWv>h-;1*4Wl>ay@Gj)fzvGSD$WN5iPAW5ol0#oeUjeuyvJR^Qo!ui|~D=Dq=*+sdIxnv939hNQWU6@A^Oupe0^AbLoqpmB@};YCgL*myCz>IdpWEtin?6M0(b_ub<0s`q!zMjy06L zxL_&J9V&&|b@F<(g%r6BlA;b55c*I`b@SnA>94x)63ZTEI>{2X)jW&CqrO2_8z*m z`-E;g?a=MK4Z3aO->>ft`}P6o{&yyNynYL(%`e!aKAAn_o8ZzuHd=(Halq_V{qXacvViq@T6?`22X{*uRF#*b;S4yfe6ssf`AW0G0|iK0=^AF zK*3r}>|%+D`%Yrws{%|+dWng)N(3|(j9+vT<6^pFO!{{CXWxQfg(*f_AAyfoJw`Mg zfT4k@7`(DCJl##;aqJ7+3;sc$6}(3;Rl{kvDIAx!hC|E}*ni-^Pw%y`y~v#H{_9|^ z2tt?UYFM_6<$R?{(m&i`j!PuT{ir0J<(sN2wIZP!Nw(c7Nq=(QcAk^Oo`I6sH%b!6 zRMSVmU3Y?CYesxAX}=^7Q!U<_ zhN4{MB+75$#7ezIwdJa)thuw=aR%QJ59vufvY)wV&IQs3K(1li5K#}v7F8?G=a$Q& zyuE_h4(GVw4E59W#?d#M%3CZgi&$j{wJC3>5F4dVvoUomp2H<&SuA%Ny|llG;k>1% z&RmyzZ{D_9ykm#ii|RHtF720yMtx8IzcV3;vt?Ak3?vVB-;rP6>mcc8ma*R?pS@pc zlA7-#sfC**t%6*7AU(CysQY|J%%$%c$$V5QS^IM&yCzO@ewIq!BJR5e4pP{?N?zwb zkfKy`Dau|UMb<;*^*(-lS|Y`Ln8W$gLrN1%rEGU6c|U}m2)Bdeqe53cWm?E*VmM#8 zx62oB-u`fva{C-9UlS-5;|irRQeVCv;G53dU8-);Z|8DMs@q4&cgJN?^9M7cmn@Xp zZztr};Lp(NbrTITzo4PMJsKvZLud9xGz!UtZcY}Otn!7PF?EEO($K=|1#=MoCV!BJ zR#|7!#-jt;-hYqwVeIUfdJUZ>kz?yXp6nnq{7j2sUP^3Vbrn`ys$gyDhpxAK!q)dE z>~bt%Z?*>Abr+-i${*;yL4)qj$6=pl2K&o_=-zl7x<9Ib!{6=E!)-l!99N-7#$GtK zHG*RvJ5m=Gz*()2J`s=MZgCa;mJEW&S}(}3yTDK0i`-9xUoOQ^pT!s+?+%|PT`|(> z6h>XP#h8k=82{ZBlLybmH2XjVg?b{m(itJw+hbPrL(D3^h*^2FFstS#Lc2Xe=n!{= zYWKt}cNhFwSc{qUZ!qK82uz#&52o;4J88jj1bBI2e5;WdJLe?)Cu_sE)&nD2H^7kl ztKm7ZEj;!apzp2}xY;Z~AJcZ|J!C6-ZCHz*Y5H)q+6srDK-ez@?A)H9>zxsj?8W=h zYLX<*FO)>DG)cV7jIo)-3hQ}i8gTBm5X;ZemBcxNB(WEFoq--_Fjx}f_{IuXOVS6< zYi+h9eJYaVhE!cD5|-{Rx^p6Ofi;}K+b@T zyFBB*&2XU(B}*jzR}Q-w^dwcAyY4tOo1T2>Z*`Z7>;e8=77GlPq^vTifrQe`xvy@*+m-4b9Qeke$92Gr! z%eCaISE+n`q9s-Occf~;LiyI+PpSjk%lCY0!{$~>Epev0xLWz$D;N#BO@wwl-*o(c zO^2CxjY983ck(7QKE>>Dy?)S(=#J)k|51a_?4Lo!FkrT9Yx@*>N9UnkpV8=GyN+I; zJ}}~rG}+T0W<3tUJm)-Tj$HK1bFhBU6gGddtE8zTy8YJ@-PuFsusIk#4&7mQ&XObwP@IEba4OR=~~ zEEX>Bk9li*AZ*NH%$nqaVCKWj_|elDV3UM6b);i4kWsY=}`Y7=sR z+aF0vgD6Rv&AHjmc`&=mIVlrWdMWJ}_@XrSR^Ncw`T(wd( z%jZk#O!Djx$e&*!=0Y84>H=agCfuJh<0R!g_1NSOG&3hjYC3b1ECxxsiL+$1VK!O? zd54G07~NMT*?H?E=Sq9Y&1TLfXZ=<4b5dBZk)q9IQaq2|g6oykUAjo|Ui#~bm>G7@ zk^Y6t@-~5;2mLp*#-1G{yEu29r^x4u-Rv2OkqXPhQgPT*Dt*WujM*twcBN7^yOUJ) z&XuZ`5zKXnm2YFBq&nAy9h3By7jBih_v7Suqpi?-IvLu7$=9hKL+3YjH8}#^6XeM^ zYNN?E<{6xS20i}y-Fz#xVcr|ivW8h+v*%GKJQrY#F@gvaVXC8V6um$t40(N_s!g**DI3GL(=jb3f z-&us-%^#un{*7?)Zh<~d4sdli12_F)aQk`;eS4duzqvjJT*!mxmIoNjjH97md~>~8 z4xj%17|DJ~f7dn`yH1G-172aWjWq%@GZ8eQAO8IK0AWpfV!pCHmNXiHl{432-I!L0 zxRH*H=l{T_9~%&P$`g_COA&dv3nKlFU{k3In?lxL2R?!d|s zov<|d3Bn`JVS(OA%rVwN$Q?5T={LcY%6*uykLXI21N_FC!aK7Ih9$kikYCXlw0Iso zO>HnhH3a>KPJ#RK8Qk5?;5^L+J-h!W$*m0}F@f`T&q5NqHJ1eQagq>LFA4chlIRd4 zi8FX#9^zejn|`^ICnRwVZ_X>kF)cV_Gl??}rG|^WGl`!}C8=8!F&EyQl1>aXOccr} zYRLGO`^>popyq7xUDcGh<*-ufqy0pEwl()wx~R1sMD=;IsJjjkbx^FRiG8TAlgr<} zih43FQ61D3)oSwhx41js9TRm!{`#Y?qH!Z1{=J5NRr*%7xMx;|OV(g_N&o04Y2F5+ z8BruD6aA^*w5K-au%sB%>yRd*HgXn~A>Wsyi0@1f6xEIsk}{!OG{1*P>Ze#qGomNq zv7e;+kXJ0?{7$B3^elM}>p)4Faglj}+=*StvG327biSuDoxCN>*Hf~M)=9R8Ifr+6 zXMVjUxgj=^uUjK8-?_-EHy-l(4CjtFL2>>%DKWQ`5{oD)5$aS*>!j3(S$zj~N?9fI ziv}y%F+yER;SlEYMM?#IN|g=2B%(9CIu0%*XEX z{kVhF^3SC@{$9WPTR|)D0DF})&~SYL^Bc9G+pHHFH(?h^`A#%Zn4;<9Byg8aG$pu zeS?b8?@VL#uZX~a{#uX}Z+I5#WAK_33|)|j5!)@`do2Q^9_+=KBh&E5DrW?oU5_c# zLNI;Zd;}NG#q6PXG4E7=gf~jVGF1<(>G&HFx@)k>X)!j>uE(}Ht+3-`1MIpJjHrvx z5cN^T&kGS{=Yd_Da6Y*!!&3nxheZ=Lwug%V#DE(s1pB;j}ve{PH<`rMI3KVmGi z_&JNX;wavk%i2nkSF9wpvX-QtagubLoI~eGN&d$`6z#T(VwWQ|EjvXy-$PW*I5T?> zi@E`^5I;LnuP1jm;~eM8MN|pABeOV6U~B7lCo0GJ@IvL;)5&Kf^WHV&9UH;7AAPWC-4Dt$UGjThy(P`#FyD=Xczbg8hVZR;H(OE` z2T97uy^`9ilcc5hlk}PP%#Y5MtSga{{p*fAS99K`Fe9m$JrE%glB;)1@|p~id}d@8 z{O!y9lp#_yf!yA8dnxg~%zgz6DcSEQZyf1Y7*Hv1cd>J_oSNrj=j7ub;B$2ul zeQ0;T!_2-q=)^Fi_Qhi8R?-8;?B}MfoY3^$OX%%r$1MNzXx@DhJuRR9zqg>(-nMAd zp@2S7AGG&kf9c`*Fba-`spnC01sSmT8HFwlccZK2O4tVE5qGph_vw4lW5h-D3^hZq zQxnmfyRgrb$#6UK1%26r(C@-a^#7v~`tKTp{xRdwzr+Z-$HpN;u)d`mR^GG3 z;=kTw-j!g4PC0~`7n)+)tk0Od^bscTzvDQ}f`6I`M%w3N#DM?cWqB6^+XZ64z#{a0 zIz$p$Ws!m2T8&iD@jPowm{7p<{OE`6+ zx{}g+v!u4ArV9eLOAI|Q&2kgYN7R_G=CFOQHKhDr!L~ii6P{&fm z9tiHwUmr#Fz=FKPcuA=s)|o}GP{3A6*I6Ua_}?Tgje2bbIf#bbn;UkD#)UZ-eJ_iq zG=jS>nqJx)lHta@q-IW%{iRZJ7RAVmAZC@7GsE7D9Y9(i<;811_Kdxkyp{g)(#uI+ zEi#tZ*Mg*Y9dp9y11iZhmNzZqs99lF-x6Y)r-#csHw*a?oFktbQI~RVp;T&)N|o^% z?l{hwbG3ZC?JCuVoV_&OmL=PXXENI;<|=WOxl*0#D?g~YsXJ*Xzdz4KgXraG=x_x( z>zMa($Qio(+|l^ZcjnYJMAIDh-QA@Y^A9sLd&{iyJmQ!^%+(vltY^!7W&_2dU04`8 zEIf=(TC-tXF&<`b*n{`ad30$!8eP2vwySRuhcra@%RkX$=Q{M<)&{-U`{UBr4z8DG zp|6t_`lr5!^#4UHwk3wQx(n|a z7csI$Ta5Z)i?MAkV*FxDOd6<0;AHNXefkJ_VTm~d2Vg;Q1(w{j!phGjSobs+8y-DD zWcFxmwHksQj}PPT+vBmXeg+OtT#REom56pYgwyo*oe7zZGlfrZX3IbLH*r5sYnI`Z zIv3GbjB#>k9F8xx$3JdsaQLn{4pKwCZ_^;`zS9xAmMq4$m))?rhY>cOn2Ghn#$wg9 zURZMP1{VDN7GauLgiOdsQ0EQ^H0_T`?S1e^rZq;7>;~WCCo#OWVDPJp@U+yH_!925 zal9}0osooDmnA-Dh{X3Qlz6Sr690#z#Q)&Whdz>oAkJiOe@O`7ZZzP&vnGzxk6xYc z9-=_LC?=CrUzse5%bd9v{IQbxqEO%B+&mKHz$Kg+Cs94LrjBf`s2B6bv?4|_{VKVJ zL82VVy>?<1_09LV&xo6x;g1(ti6Wi6L1+3JoNM`g$WQFzo{|gHp%E{cWi6>z)Guol zN&0~Z$+#N9zMfdg*gc85XW}Z=`qZ8gr|gs|no*p=!#TVUiPabwa{rwnese=oI`YR% ziKSf3Bsai!;YdI7b3u~s8^i9(DU#NccPF)|nvZJHWHT$*%$eVpS-gCQrqk!0!9RDh z{$-!t$tCh)9QWI*T;^eVNbWD@8HF;VHp7mcN1^O+;m-3clY({3q-}OuiibK&$({Z3 zX2T6C9{X#j^tjw7rUnT@drDd&DjVod<*k`F8$+_C1CDl8~%lVQg z-^O>{)Sc9`(HkIG#Y(~X66U;SBq`Y zwB;P=xy?i~n|w6Oieh#Mv)PvpfqqRb`#&Pk`Uo|Jlj-^P{RqQD^ybK8n09-G&NlbS z&uxZviW;^)chT+K5OgnWjUHuuvt-+$SNSQp1dN62PgnF!D1k>w9Xx&4WANYi;Z?W; zL(`UH=+$KyYLW=AjlS?2RS2()PZ(zT1jFl2!^iwAMlL@Of4g-U>l%vj?EIK`={Tl( z{)rj0E+K?jeqmMpu%LVgmg=doYTzZTqjzq@Lq|mB1Y@h?LF~A_1b_eKj(yilaCk^1 zj@vxP>Gm6NE~h&#elEh5&pEhu)*LYdtPrCUiEG|Rah17YSNd+lrK(6=9McvT`fKBC zq7MFTWP@nqK{(#(FC3wV{=lcF*pq(;yENI@_PrH0Pg{pg`fgbN{VLWthGALGE-Xqn zz&wL@2wl7yGZU{P@clPT%;yby`w>PxT?rqz%NXW9UE;2`m8ZT+d0HMI@ut!8R2?r* z=atCQEfx}Q79{aSiIU*6SrU5QUR}?9n!JLhvY!fHSD5QFbpC z<^MeB$3IhVEu!d@D9NX%)6X6$imB5@*^u+8DiKvSZ$8KQqB&hhKS4OVhse9Nryo3= zdapOU6F2EfYHRXxlkIpvrjb90kW{0&oE_@0oy;ZWpJYinvy~YRj*|MCy38QX9Cum< z^_m$De4SnkWiMAExrs6Cza*}5-ds}uTR{xdSkjnL^o(uMoaE=Ey+txlb0W?ncu*CK4yu&p}($l9z8)d@$%NB zOy2GclCsCO@;)s}KCNcAh#$RlO@_-ie`Dr>`N{XgljQrQBKbaQjZ~k!CExxgrqW-{ z`?Ixt4RMmHPt>L?QcLZkHS%ju7_?^Ep~26|XgFsfbWXB=2;2A@-t1egs-hXEvT`2--B5jCPI4*9|?2j`0aF4xuN&B?%VYg<6aN*f@TG zU1=e@FZM!@yhCu(afNg1ws09g8?JR5(D$7l2H3^J^XmwB*|}qcvmtz&-^56dTJACO z*XIAhr^9o2A6)_;-Er_8y90jCBj6u)6Jxc>0T|uHM82`68uI;iY%+pZ?LlaLBIb8| zgT>oUVPy|PtP798hMCt9x#SVHJjlfk&-RF_?~A<^VK~(5CI0y|8K*i9#JQcxxNMMv z>wjFuZDz^dH_gRE?H_oUcnc4&kHka6EqKsFiTeg_xO?^w+}5BiXn%?q0nJ0l{c0oMMygB3^DV{zXR zn3tG~P>(AJy6T0gJ(^=e@mh>+p##4MsS=y6El*s>$&;cYi5vPr;s)}w&k2c3-!4y| zOqcj;+=ZsO5?_-k@&DLM{019IFe1jXBubKp3=u{08c_!IqsELkVML`UIuK9M<*n&L zZOSey-k8KSzv+qUU%uPy>_okWn8_|;nH~AwYr$D1cdlslNRrdQjW2hce;_qr z)Xi)pC&>3+a+HT8SJjAOfww4o?}nw9nSO*GbZRX#vi3{HX{BWF zy_inl&9fbu?D3;!`V`-Lk8a2_FXr0PFPMIm`PqK-!IFo_o>eY62MZ;4v#;c(FoU+e zx#Uf?lUyG^d2yV1ns?b*Hk+B57w5}MW~CH%camZ)dh7nCo^#AldVLb*?YC+v3#^uR zkICJY9+uC4L`(T&YMp!EkZ-#`a-Kq@W^R$x?A0e1ZX@4EYoywID{oI~R!WF#_Mpf7 zza{d$#7}BxkCR_U+R$RIPlFZS(9Rl&hIMK*(lvsvkr5h?OF$E9p!AIHGXJ9h%|{GH z3#S{0?;mj*?be(CHXxRCOewI+-oH)2UCMFYZ`24ZG^qYPB_#* zM$i3&(CbwlT$)D0b)7r<&QFGi@+~}18o|q`8s4q{gtkATF_eXNyKqx%3p$K`8zb!`+yva306;L;JkUFz+Q3ThC!pusN_fz}ge^ioZl1FEriDJ|P zQT8KF--EYf!d6jnSE#Btiz>e@IrA7%CYXw%nlswRPm*t(ljH^SCE2GnedgNa4ud6G z_ktvMrtS$zIdNK2doatrJ9U|SBW3(x|4}ft&*CQ;XX%YC z{mdQ};w<;bneU5`XKxLtBcuP-n(xUa+>P(q*V>YAyWf1%d5n`hrzpvvQYZOA%xj;^ zz7d~e@{bKdsNDgR?J>s%o+1Hkm|-R^3D4_ah7bU{$4FL zr}j$ivU5_m_JY(Guw(L0AR5%Vp`mq8=y=USqqQBNn?DjwrcFZA_p{N=w<=`t=$K0e1l~?c= zF~zO%me?sn@OMQC_Ek^i8?HBw>)PSet00_jaS@l}R^i6azj1%tD#SH)Kyu?bQ2)w6 z+LevS=$wGe@J7hY%R%Pm-N<u(VN>RMM0os()$>BI zB+(u7_wPq&_DRg#)Ea^Rw3Wv-3+3@KD|xKu+;t6?*p+q?`^ib3ByN?sw}tXlwM^o5 zHS#pyLY{sdFY$Z0@6HEOGe(YY7vFjL7s%P=O41T)%BJwg{@X0ceHKcxhI-GfuA*FC zFDfr`{LlTuW~l;N!L}P)Up%BKYYuz<*cooM2`V6(qnriX_bK_{Yy?l zoAVgVkL%QBx0%8_wosJYtf(E!B##%*tR&u?T014hwUF8{-mp`-OB?gY|EBikJ^8n> z_UtLil+;zcG0EenH0RrHC%@h%T{QpFZ@bM{(miw~V=KELrsYcJ=_Zod|D0s3tdsOB z_L6Qxti|>!yJVTedDvYtd(n@ugn68=n@iT$I(dGZUCN0%lD9il@^{ab{IFnZGuZ`n zXt(6fdM~*tdXhJ~Q1aV(%ge>i@~R?OiU$+F+%QPWqV?omSg5?a>nHEm#>ht=I@9r-0{ckJIViYkI zJ*j=j>=bJ~`So@>u@zr5Xq5-;LCh|WtcT7=<{B?#AEM!XG>v1_^X>EfE@%u2vb|(Upj4_S6;Azx&1`ghZzz)3; zc-#!r{vC!H%@Q!P!&Usr+@IOk)?!}VOoTVxf@SxXVU6A%MCim~)8y6ITor}w=g%T) zVkY*svc!RU4;=N;;H3UdoH4wL|HfM3YE3e3ce{ay#m5m}vJ$G;JUsjH1I`r#73W%ShE6&HLaD{u=%_@{p3miG4dooQQ{8z%hSg05?5<2 zaRq$y1!yFGWx6C}lJk2^-O7hbdQrzoV#`wUeJ+wXm|TMAMM<8|J-CqXy5Y^Kd)~+l z%*&!`Eac1h<9qlfbl{$IHDn$c_he~5VleAOv55K@;+Dy+tRyKaN0NN0nF*Ooo+Ch# zCzSE-3?*i>n|vVg8FFH(CGMhn;xB6A{^}u}h>!C3P?77iy1?v*7~Zk@l2S(R-9Em_ z`jh8A9wnOoMWTN5Uep2PEAq(8b*PopQ(^K;UhvO=;YtGbC~7iy@*%$FC>BbXPhk^EK;lK1km-3{*+~xgg1NorvmQO<`$>&sh4tj+$ zi}sOx?U^XwwDhDp&sb`f(^qi!fz+<4m)gtRYkjWDkE7IIj=CW~R&x&)GrMU1QTf?B zLh4)`>A?+?`m8h1@^L|fMGc|NZX2Dc?1<`MkH$spM?9;GX5PFrv-UGLc>r%r_8cP+ zZ9KQ4o%#R_=^Zn=kqgts^v?C64$RXGU03PBjy+x7qn^WYzax5G0bJ(fz?Hqo?icQ& zKU}~g0)t0(!0>-Y!Ef7WjLXu+r04%&+P&=v`n?3f+f)cH%tmneTl_ga5g|^;FspB8 zgq~C&?Ajg7+xG_+Iagz8zw20;kcD+ix#NP95qa}AwoVMg&Ot}9o3G6M_8#N}oN=tr zB}8W)#o4mkxYVyDVlsx|PN5DSZw^J`MSWP}6$!K@biW0egQvMby_r*q)k(%@;RezRJYgmN@O*+<(5rICo3J zC1x*plY_9TA`TiT2~EQ#zOIJ8R1ZlS!gpVdxhN+EiOQ4jy5o1~%~>eQIlMc)R!MRI z^`Q0CUh`H}G^F1sfp|)TNuoG$nVut&WIxVbnX4obGf6s34QM9)2P@Q~Sd%Tvn{~wg zPm9{inK~D5a(s{I;j1S%5htor@)G^1k6B0TbPKUj4fSSe^w{m?+iZ|K^(O~KGcAVt zmjF@kUn1%c1^oHbqRAmfnY5Q(3d|Q9wplV(x0j5{F_Lj2hW(pSlD>Q)F;4Eo2yzt> zMUpj*T%94ir4!hHu!3C(lgs4AD)x-*U`~4rrQ{YkOYYuP?AHpCyb|)}AK%N%OYZD{ z-7K%xzL(d#$(h^QNofSVt|QnHdvAz*a9SlFHSB5*3Xm_!*;1ijE0xpyQ;CYT(-zsv*si#aLZW2LsvS86M-@wxe*$(ue<5r8`#N_fab|Fp&vL429|%Ib=Od|UA7Y)ECro( zFTj{wgxReXusAywU2+OxYxWgPbwH1yU*Htn54|_U!`00N?)1F$+tn8X2F!%#yXWxQ zc^Tf{Uc$ff1}1o?V(Nw*1m%VyMA;Iv`xGPW<^s&Ayoh;yW?}xQBrKRX4GZryLpb-+ z(w&#Evc+SpZI_GnRbkkuF2m-g9kK27dh9xAh&@T)aNy@a9Bpla6SggIdb<+mSMSE< z!Vuh8F&Ouz*CY1DQY3w7ht%mGk^N;b3S7^kq&NWYkMG47oBpVr_6SwpWAUx=d{o`O zi?1g(qoU+Hz8tB*$E(IDtH?#k=uddnDh4lhXd#mvhh~2hC~UXm>Elay6wn`co2hX9 zLmjT@3oh(zg)@JwMYQ)m92>kIhwW;x?}t17zCH*${^Z^15r<8)9%FrV7FOT8g{7)P z5;t)#=W38Vp&vYM&S{DJJw=}OwwI^6&JwrQQJ$!S!XV59-=P8`ma!KA|Ey=Te>Gk36^DU*aIx9{6n4e zEsdo3&E-39x}^Nz&YK@7nrwO&4pQTypwFOT2{UfV0bZ+?^o7>MNW+L->PSY~R>@3V zAz8`*$v)1$kwJF6F$*O}*!A<9+}+(UdGVWk`Kt@^;&Ytj#)nCM_Y!$|AW;f50rINS zP+kY_lwvn#s5CK<($&<3rO#*1-wpX-oy*LWT=|?4#E#_lQgLOOd~FmURilZk>@AS$ zvIwc^RwqA(ACp?68&Z3b9)k|q@^g)~{Jc~nKQ~&*&p(>T&!vvc#Y~gBi=Oh!Yr6a@ z-!H$LWkYLeM>I(7iH6%gq0wLQXdJZ~O&7jJv-Vx-x3h)*ijC~$`AV*Q1lm!%Xt+zC zS(rvJ^`C^!JxgHay&PSS?4!r?Askv7qo;8nIJ=~wk5?qz*|XYj<|lYiBL(Fv3_iCG z!>4&-K z;O38gxS#2SIKzQR{xJz@({3QA)qK2qP>#14> z+)+=%^%MhK>3<*p>FMFD)<8s;a`vt^#o<4Fu)pm>{QaN;I~G5|mhijSq`8QQ^-)-} zH%FejJ&>nO@5xj8Sf0MqmH7GO?3fSo^pTUqCA;(9EaFaVB2V5}NnAbWtjlnTFQ%@g zO{OI19F+KU>RT=l@2rcLMDqudR1qzT4B{T9#QS#)=jSj{w$PR27)RcZ)OJ>BB=Hq9 zi)J!EH7^8I+llbI79%&=|3%;zvk^&c;(%@2#_5P3Kc z@(CtN(e&WF`CS#wun^I>JBa4kMoIlEMACK`%d;N+&o7(EBEAu!+3cyf%)w-=JUPhDY=EQk{@g!FU!;9)%siV z+B%3iw`o#x+e%8cm}mQzo#kZ?^3I$3l%Mq7DL%{Rt;ADILZspd`|N7ROI4p3?zl0@Qy!slfE$`RHGtl$>u7%bBKuEM*sZe$ZQE@}`&uQonh_P9k%<+VE-{4j_u0OtGyLmT2o8evNQU=iG#;s9#_k|Vo-i746V(B z&%HhH|1=ciPd&pFO*_mS`~D!=FF_xU%mONw=>`Pkj%Ht zp0QWn4Zd$Tkpq<1^5Ylx_*teYzueq|-V4`CUWJuLtNWW zmw&JB&uw3dh2nz}q3pg=s62=gDg}u`WvQl6o{=FGvmOZr!|&WWzk+|Sf6qT-Gx=Ne zdj1^nl#4ybbFNP&f5dx1lI#=9+FCej`8~r1( z^Y01vKnB^38`t>iva@_~=0ZM?jM+1PKeL0=ev0YSiDKr2QcT@YlExjP*r8@5_45U1 z#fqY>@+j(%BSo2F#tgBbXcu7WO9O$ew~%xQ?kiS%NxFSAN!MZ4eB(;7pR!2)2)KcD zH~2@O*G(^?__^p=4VW!4Ps;jRQ|wpxVgAP5WPKFL!JC%nsl#)+o#g)LYXfl099d5C zm>i13To&g7Oyco%il1Q#er63NbsJ8}Tf-?u0du5QK~y`nHIj3KE;tf8hJr$4lde`iIjEk7G*meqwMdEl+#a%a<)IFoa`3LofM0j zTS~cpS(LXZnDX~Mrh=$ODjFP4#c#{0)WeZJU4w_%YC#oOJJFYu4pe!`g}x@)&^NtC zsy=y+zSo*Sa|i71Svl3IkD~hZv#B9aiGHQ6qQ(!g^xHw3n#N>MQ;PvLXQB4iVc)X_ zR=#@;wO|+c^Q{lHR-n(FjHI@=sK53N)Sd_|PbjnE?PgZC=*t}j!msbU9(M|=W_71o ztog7TcQM|=-TKUhZ!-KN2RL%y(Q4d(nS=*k-_C<`;bSnNJCD4(g2$?TV^CFoB z{vURs{#MQezf+6iHf^OipLE=3fZ6RsUkgDkT??e7lA)9&$H$kA*wKK22jX*;7)q$$ z2|kMjC7eyf4rf70ms7zHn~f|5OG=6eqomRXN}i-b$(IaJx2Uy}CTL!Dpvl93?^6c7 zZ7rn?8Ux=IcpmL;ptN;1;IO#U#|hTp!XTFtI=-wRWC6WKHuK6+$WaDXpg$KrD^8S) z_m8}O!13H{DSx986NMVf12&X?X?3Ds)dtkKACFe_zBoH-a%-Wc=FQZs zl0nT|q4_!CLVx}qptiAE)b=EY+MX3q+lTqoZU^p4pHNn`Z(-%ZgIVPrvJhs&+o5?Y ztM>qI{yG(z9PqkoAI-Y`6u9@G@vJw?hzH#9;6eQ+@X%|g*--T#9(`sEkM9@26E9ET zDTy^az2jdtvr=P@8^*It9`W4dRc!7zmo1_O@bX@Fc=aJ~pOPQO&Q!eGJ^St?5X(He3dya3l zj^Mk8Q`vX^D1HdubkM>Qe(pbkUu}@{`$K*#jatKrBl>dsk6xT-p3h}-8o27>Xs(kk zTQY8NE$1&6jVi*NwYMS~_&l8GG z|8Sd4J~tjx;vYNBx$>|ZmxefVes3ercN z%3dXuFn%8;Jk+IxK|%1fJ%H>j@UJ!7DYiu$p6i`}IoOlzHlCY+T`wB`4FiC~k2E1^ zG47-;+esFRyNe_CznmKAiQFmf6Yz+dlekx6pMx$hvF{lApMO%~vUBkNtfr)~z~vO@ zf@eLB68_-cGIbU1CfEfJs-jMTC*V$-WQoTC14?=x4lSN5>J2qlg*merc!HM;C12e~ z$@#c@f?JZ>1%515TPf|+3Q7;#Ngr(-DWfm)-ltho*2C+R-P%ey%_Wo@iryxvpu8u@ zZEi(w?9v9x$9qqKOAHlG4Wh!}U@Ed~r{V*XsdOyzNk$>#K%<;0g7;G8tcmpXc^*}z zKx_BNpQ<;PQS}c)_=XkHcj$>~;$7hJU`=&zLaAYMIQ=4Z`gPs}yv%XPV9=z-W)=Ev zRzkmB;9GPCUeNFs{L|(@Z4-2kcW|oX zj<|=Y`FgPC2N(E@!yD7TGqQ7RxOd4c?$DK;k0nF-=8Fbfe*T=jbwK#7ruR{ zKi}P;&-ed4;egtmxFO2;`MLBR1aJg>WB6Ut!`aJSM8;uJADy42L=e8n@2_W8@EJPzuTgVrmN6Y-7Gpu z--{0Ge+k8u1KiqsG&k&8!PPmh`Llf=F8b#%dfqTj1D2Ptpopab=Q(nR1HZmIj$ckY z%g>e7Ip~cK2kz>{{#?#J3xBW|^nthFn|M=cDqmZ8nJ+_scme;u(%#@j&xs&;$72+? zt&-v%H6aVcoMed^6k8BVu@iks`ooK)O}8kv|2T@B@{VGcRZ;BOa*74FOm;eq|}rADJ~j6Z;d|5;BO*Zr;E&z8uYZss8u4_f3_quUPH0V z4M@7c9UN@v9aQDOA~1h;00w9tKyshS6lbYR@nd}`J`h@U4{eH{7fJEcaNm4mO$nZr zlxPFoE^RU;LhF~9f?Cr=E$&={+lwY8odTY)pbsUXUnO<*qNMKF3vu5_9zKzh>mn%y z_tTWc`+)P|wu0AdYA>SH_#jI21^3Ja92S!r`WTr^8LRaua}WF+{vf~A%aHPvwUH-z zlJe8WP{G2Z0W`+E;?K)*76GHnTm+)L;14Hf+E!f=+3ILL{;n)xIB$9Y%&<1*kKRTe$D z4qC!i#hXIO#Zh$ByDcl}?@BFdCos;hW!Oe6SM}_3_ z+xm|D>iA23q5YGC&u8=FuEY63<7oD!YwRtX#&=AE*~9k=-}tkaT_x}N($;>shhQ(` zUL^Z+3w$j-ikl6-Nm?>E&k^tnLoYh`zaE7<P*)=_q_XWq~ zwI!YxgWk58;`2wJxZ|Jd(cvre_?FzSn}BE_IU4Z=zV_w8d6I+u)Dh znFsE5SKt$)@cE$m@SDKz9cx6gnrf1pIg`Aik>Xr&6OBj*|JtAA-{+FNMgdshR@9;r z`XF$*XSI}Mgn9Gy5lYI$4u_tYJh&4jRipmg0w@W*l*AX{Wp8>%iDB0ZjFrgZG z=GgsC0pELlfEsX<`P~m#`eMwP9RsPghZ(Z%8mMg;`ksR;wOhNff>t0a&gjcZhp(`* z>nv7Db79q&$YndZo;7P`vi3D((WyP-o`j5$f8p7j7t4@y@sM?1JVFBQ^M++Sz6j`C zf-RdM2T#m*1h+SeEh1BRm3}^3uX@5ZAyvG&xi@e5;LTfyoaSxf7ypx^!Mn$QX1gqF zwqLW2&s-bI7Z0nko01FP$@s(ftw*t+|43*A?Ad4j7ryJ_&v!nl^BuoX_Hui`-iMy^ z{grO~z;rV|Y8cPY%uaCF-(&oC!gh|%s^a+kE1W)fCFcgOQwjI?gH;n&Zxju1ae~4|`S7dv{l%cj><9FI_1H{CXtxJFOJ`t9}Z-kP)Ky&5oi+ zUbg63swg@~76`RH&O)U=0DGYy|9&}!Yx2%=1#rEhsY;xcl)$M2yK%h15{`*J!0*3i z@aqNJIqcyDe)eNJKUtT+4_D1#zpSh5m^`lh9n=fg5=|HJfxE1Yoy?Qs{>99?r<&pC}0n~ZAJGf|rNe(_nT)#4k8&pa1$M`*sbAU0J;C&(w`XJ1+ ze$dCh2j6*rDDEt^(1Z7<R_=l8Qc~ zrt*#;r*$8dfV);=??lCo{#0yYM@6g4sSy0pqJGs>46moMC&)-xSx#TDBUf4>&!=K3 zRsM1ZcCJd5rz5G-+lMMYt)j0M?NnuyOy7P+QO&uN^wY?I>P-r$VH|jv=4H^}HBtRw zZK`vOr@DCPgO-6e?E##@w}D#p9pJ?d{0=i|+d^czPOqT9`!rd>eJU&7|AgG1v8x<=u~hc$a+!?_N2N{|)uy zgRPhNWb9l%JHL%DuY}f3vy8pfa@p^Zlmoi_{JJmkhuD0UR~m8ZmgSt&C!b5yeO=_B3D%dm<_BAN;Ia-XwFi1D*hXq?8DXeLa|B9WY;> zUPZDFi6l#D!F-9@8t05(E0F93-UE8zdG3}Zf3^m-Uqf>B2$GAvs6)(~hoRg16h?{l z6T!K}zBcsG|G!hFjDkPko={4$T|_C`=O}puZY`D2@BM;qcJwVu3c}YM8lYbdrnC)X zDD431Zh07`Ez_j5hqoww*LC`M3fyNmcu2VFQdZ(Q%$Jpv_qz`ju7qdG0#z!x;X|cX z=crVEg-WycQR&I0RFVyyUH&L4^6O89pF+_8Y?1W}t=&|3wu4VoIdBzK-U!5;8A6ph z$dLS}k}7_bP(?NHJd2@J*{_Pe+E>!ITOm|KeW*4@h3cLeQax^*^}mg%{)PqBrH4|T zsXx_)PJ>R!ni{vbQj;d|J@X=JIevxyvoNR-UH9Dr;6F zZ&ZUj{y|3f<0r^wLw;0s66@$dYd0C$J|_=ye~srnFj|d=+;rs;zSDTjoOEzg}Pa?9}KqQ zz=1tDXjmmb*XYOLk=6Wucem{KHV6n;b_8#g#5X^_{KI zT>M0IQ*;zP)8C4Im-YyQicB%=%55=HGDVC|T_(o%YZhY_eu&XmX-SZFQQ5}k^}h4Qv1{HOLT{}kr@#pEy-nRMbTvz44O+KJ_Pz|y;H zpF|?HCnURt8BhK>jd9?(3h`$y9Isgc9I%` z>osOK#s0D(*#_J@PGO#Wir2U%ZY5hgk^DaD@0|(Bt4;z>!z{WKGvDDim@83B5x~C9 z55W5`9XyjmxS8}P*>v=&ZmlFUumsl4`h2 zYcUXo8*e_=)LyXC3nITW)XVr3h+N$fX~}f zq6zl5Nzj6qKsUbM9G*spD8@3S$3 zDz4PhXTLi7JnRU3qg|-N)QrBk`_R|jhV(7Gf@&u3rrJFbRHyGq_5H6?{jo@B6s)Mu zqloIP9jJZ^_P#1rYV_Mjze~GOQ~gkCnU6Vh;2UZmiaVylGFJFHgB8=f(d+zKIdmnf ze0#;JS6!fe4PuSu$*g5MhPy75vd;ET-1GcL?vpv2`&*r7gD$UmNV$v+)w}SR&kuRx z^%$Ps-i*f+qxocV!v1qX~g z#et(V_{nr#eztlrhi&`JZ}tr5=qWxNf2WPp!Bfju4CGHARJc0z2{$eoD-`C{hz?ar zLZkMY=w|35dY#G={p*g4A!c{P$dTQ}xV0a|MEh%E%KB}>IDV`!_V^^Gbox(BG`=Us zZA}p)gTITRmj4RrwNFM1z@;{qVf8!`_8ngte zk>F3(lI$ojg?RL@y;>BzIuDOy6zh(2V$m-MQVXtADsX^QnG&V>?}PRL-kG0}0rZk6M}}Lafe{s+yH3T|Rp3Jl-}SMW zEu+D8#vEC!2QLQ?^ta7;Xkre%R6~V98?f6!>zak!ALC&9d=l9}>6hrU+?zf_w^Y6Z zx#fG2l`wJ<^2>GUGu}tOV6XdHZba4dQmMuzl4|#jp`T^bsP2O~)vKcSy+HQJ9Pr4_ zkE8lBIsJO$NWT}jP!n*~=C!fZlAS}X*)`N&wUrgtnX=-fDppc6W~FiiR{s2hJ4}QQ zuW=r$jX>Rn8?x5To7^=7I@hmHxz`Zn+#FHl{y*Ro=F*>sS`A^t`&vBq@m!vCLYGYj znzN+*2`@Y#W1LWUTjNtcXr0autFN%rL2W*33LV9yyAxg9`M5r@A;u?GY9rP!H>9*pBNA0 zVABsAYNNxi?jPnzwO=e-_?c5o_H#D$IVC~yT&0%B_34-ehjkMw5#NP++d$rG-uo6JSYA)Hw_my`1Pa_q5H z{66e3zj~U+q0V18cw8kv-tEHy&06f2P{TeSZ}Z*Km+V>kFMC8yVYlsdBwdwA(z#nn zdL)XZg$mfWaNB4e1+4++)eog%%lxPBpV1F zEWw}USvQipVqbe3PSR%7pEYi!zi@wXtt46QG0d;1qgX>c2eaeKL(mFAuczWbaidfy zZt!E^=V936Y)B4XjC>dNKnG1ce=EuQgut&F_nj8ZuES8bZgv!R1~;5Hz{p#CDM@1- zC99QDimn&xO@~tb-XZU&6Q!=WL@BR2L1)*El3P9hZ{NGI8M=gHlst10B|qy!DW|HD z|AjlIe;%dYLET-4M_I8UrQNBb^h)F;xXz`lt67v&Dh@+m z`|ySe4{xV}MYwNzfd>s-GT%Fk3cwjHgx^rHK5i-k`<-hmm3_gZv;ZHCsO;`+DoxNw zo^ly_r8bp4EvL^)m*~sePS7U2q3<1c(~sfdR9kR~>IM{0T?%S1ZWYyW75)5%Op?p+ zGMb0{gXOmLySWcF57wj>YddOHzC!IPOMx8_D?B{OiWW~;u@$?WERdBGy|}~30`90# z%W6~ISR>k+wE{OG$JB&-tVM=o(rwn83eR=FJv?aSO&;3xfJa*Ez#IJwPqvuECOHdv z=J6N2u)E&)_RPOWR;bHvvV;_EUqLPEI-{Rn{Klu6l z4*YV>eSY(1BS-I#;RMNK{`eJ|0^=&KXxHMOm@ofi-v$RIPN-$M2<>!<=w)|W=%4*1 zhNrC)YtydrMkGF-u(t|m#*LP0SUd{5vfp70JDg;bHjvCi1zts{8#~O5Yc}Klk6H2@{s^~GM-OyJb|M&PBplj?1ldy?kfXgFq=A(ycm07O)Glc zD3Z&Lpbo)P=`$3yg?fLw54TOsq)yoFhT!|pqF+Yo0%L?%lF@c>VzMZ4n*}ffa81qw z3-~w=dT{vbxvPNZRZgkCL@C%0Qw*+9N_W)dRLr0{sKE#5jT@{f`3k;1MH`xP_}2X~b*O_Zes{_|Y;gLwciIKP<+BkHK|i#iobFn5lEp8RV! z__VJArU&25j6}*^1h0pSp;TbMii*OtsbpR#mEeY1V&h3AePfVQVot?LQB*ASsJMp? z6@QacY4IB>55WAHl1^1$z}Iw%pdaA8{)E=I?%80fpA0PTOC!~d!;SNE5Y;^oq6SBJ zKKR&B)2S+I=?0GG1MGEnK>-}}fNR8~TRH-ObN zIq;+atw zc;TJ#yvie&w}idq{Wo6liR-WVbkG!bEIP!eV=Vd9WdnF`X|nyKReW;FR(7b>W2f5L zd?BWtul96jw`U#Ma}_upvN`M@IE({dXY-TSC-~`2O%4V=|IDBVhbX+@=Xcj|n6)cM zY(hUl9ml3CaY~t7*m29%siRU)7US%&-pCWng`Vz#a;$y4BjVF6JwHg zSp?45G1M~nW=HTSG=b-B5yj57C8^grlAgdV1=xEmG7EzZP&ZwJ?8L$Nt}5w_q>HOSQor%c8h8+rA4nou4D)MFG31Xo)iw^+w;6hg0E=a4PDq zPsK-ksWf6KmHW8RmojVm)@vetPewjM?>hR~90Pw^YpM@ZM|L^p%ZD4N?!tVk-#L+f zfnWDqCx@E5C&L#S+|0a7)cy|MeM1_MgIUcARnu`(@ngl09azb=nw8z5M^Nd&szt-N zQ`>FUK-Q$z%+cKSp%?eq(~EmgJI{Kbd|7`Rav0lHd05kN9_6@#$KQv?<($cE>h>CS zH-OFIH?$)2JKHSY&AZ2F@u3xC`NSAKKGk77pDex3C)M|}eQh0|2t2{3_Ghr8l`)^2 z<-;xsEqpa}C%e_#u;=;j>|=R{A2dbq6U?H)-)Hgj1u-1D$e%-@vwcBJIqbeSzcSb5 zx9c1@y5j+kpWwvli4|P%p@6^C>v03_9}53Pi;lx=g%;%q-GRYkK(}3DgwiH4K{i^< zP*D@}uPqlV^g4=-+EQVAXEl*OJqbHug>Q-rl% zzF4;GpqTUFyD;f|MvOc9R17)vQuH0SM|Ab_7o8sW6AJ$>Pt@ZCdvHzA z>)=xyI|lloX9PUl34IEC;>;ryyTXB_n~g|1=p=5ZsKq@qfd{&gEDbxID!#`PeYEEq zl3CzhvTPs4Sz&h!38y$aU+j>WDOUiK_)rP`o;Sre+fu?6@SeffPu#qLl9V1(QafsE zNdcwGkr@dbB7N>m=)`d|Icou~b0DP{9if!<2Dr8OPzrp^l55fPKF_5T)O$*JI%ZR7 z5f=R~n-aH`fwL*?Jo;rRJRSa-MjuUQQpS1}%Di1mSr0-ed!;vJUtL4l>-SOi5J$>t z0C(*93d-7OM_H3MP?qBX%D$jYxwFkFU#y^l3KQ&ktyK8JmI_b7>tPP+O~H~1z=JN- z)TbhYZd6=(jLN330hh&=DhIuxZxglXdp@)Yrohc7&8GT;UR0l=MD@=4R6ifLmFuQZnF#5&4YVNR}dok;eAIpQjjpX4P?|9UGH69<{g{N5E zV^jZ3p6RiK%_q-e%f=eEj-SEX@2B(LT6;c_7Q{zJNAc0TWq^pR&iL!Ret&8JiknH5qI+&nq4z^7hKcpU zDEF+G;qgE$G}d^OG z?61ff@Op|XKE#pWm%Z9Gj-U5f!%tpdXB}6_eyi59k1Fbp9NDwDGvB%q#_pqMlXS;K zlFqzB(s8jQHC;tgr~V{OA4AgQ-6Y+QUmKQ_bUJ$Crkx~RsX|gS^tN;DsLck{oHoVg zqb}1LF-s~@Y|(6zb;k{Jiz+YxGjL#VZ+V8gdtm_0o;OKJ2YV!XX}59EB;=9op*?OT z7RWX+f_JVu_Dal?UF@MvaEC7oc%IhP;8la4IW`CS9&pf_yTLD_i4wN}Cx7e=j*2ZM z%_*nk8TyoR-HK9MeJQOO*^$$H;EjS^ZDlN_Y{i{rBrpT)ttqa-l!8$|#n}ZjX$z%J z10U>oH%h(aiJj1!QnN8*b}gs05vj-rYNWL1m^HVX{~u?iK7ul~#ZZQqIc0cjQN~RJ z%1DG?r!M>hXYHj-t47Mab&IlQ8dLVZBFbrTqr82B@*S+GKxHE4(XCXlcrF!KR#1VY zkqU4xDKzOtMNt!}B;1ldsli|D@ffN+Y>B!Hpzpy^RNFOz>Xf%ry&iD84R%z&5xp-g zl^QO>$FC7Q%@@0=*=sKS!M|Z^+X`wwkW7D{ShB(>dscuqq{7nythg|cm9%w{zkZWd z^xtuZBoFSGFJpBDWZenuciG3e>&HayVHn4~f(%$s62${5M)085b9q?(9UcX&d%VwP z^t<(J>b!$z&e+5Y)(zohvj+3p#8dE1e#W+SZoGHYbhhgv_#il4N4%BUUJvtTXJ=>( zOxY=1!p@gve5v>XUvtT2_n-v!+L*__56|$U8LRl&iBTN3uAE;XqbOoPe~uXL&k-v& z@#}MY_}!ux90mL^PUA4AhAMEb>^7HMP2gI;e%v1MQgo!NqDz{m=xw$@49fKrW7{Rd zG;oM8Z~i6L`R)>XKK>M^_QeV37$L6No)))0?-Je_gM@GKW#NCNUijT~6F#cdO@n{!x8Vv; zea=0zg_AqTIA+)pe%-L1U+BvDsfh&#PFu--m7(lodKz`t!k%$aeCy&o+;hK@)J~VA z;77(Jb)%R^wiHuiPEz(FDZH3ta`3p|NHJIObx&Pr&7CObAo$YY!%NeAkxeoV_sT2S z*XpnbZpXubWH+)%78pvh+A$>WWsKaC`It-b{cTw!-Bw1@#e$?^K@@up^?79}c%2cr zyP!^02a{}=1)c-$iw!h;)-x$Sx}4&J;oo-zc!4KoOGA4~fF?E337nG{B}%NhL`ge< z*=@ky=06`;pV>?4zB-g%x|u$% zcA}3tb(F!Dlo1FGx`{Srbu~dQ9`r#L8I(Kg3gv#arM$WDeK@5_c?m_7j~dKh(F^`Z zsg!HzlMD1g8cxW4t)r^Sboy>+M?VhWhJyEJ2I*9Au2J%|22?L>b+`?5k`1I(K0tav1pl{S55<<(KFa#yhG zo{QY+&K6eB*5}Up_qofxe%x)d1NYcFfqRAAWxZiPc!2ac4|?2!Idc(@3TWc-Q5|{8 ziv4VAo63^BSv=pn4=?$G2$z)p`7S+j)NN8o{qC z^ZBj04@a6evb0|}PH^wbACWItkdw?`g4c7yRxP1;-BqY}>MFXQ-7NYq`ydR%y9(pF zM`GUK#bT}7eX(~=ia6bnA+E@tiaQa@M4;1j5jM(7L=HYGWC#0*grN;0-ZE84b?%8b z{o6%wY9HY{-Ade)=m_VvKgBWqkz(_EMX~treqs9Tv>3g!rx>sooX(@eMW=Q<{=2Y( zYfi^=ab#~!k9*Cs3OjzUWXUhxLipLV7Jl@sko`+Fp@-kYcdy)H&zfg^D|jEfKSfOd z50GAog-0K_&z5s3W*KVjTr0(#fELIo44x`p6kQHYki3$jz2Nuv9(ucja`e0^)E(|C zm#j$M8Mnz1xW)WN4|`IMdfE*PJ_a{X-2N|DV9x`-paA`X1?o@DWblfdLXaW~l?0}faS#cy+_cf|Vkvch4Q?V@ly=>k(p2gwR0=LbVq3Dyq7X8`D}4bzOE*04YA56(gi*Q| z{EfPHf;S89o`wOGQExz*NtiwFVaHpC9CYw+a#mMSjz2U*pW*d4shx760nD?6*Wb-U zRJaiQmT?MHI$|$<+Tu%}KVr@to<&tts;QdYsAj`ps_hU&wO{P$=k{B;sbJ1L0YCQ9 z@Kl)y+`wL+{)EA|?{F}+-#I{kmuAr4%klKrHIx-P#InMhd8`<{86FRQteoG%9i}hh zj=lP^n($G6!;F1{}a5iRGuB?`@}Bnt63V_rxU-zM)C zxsr=QT2v=O_NWPO6>D)R@0YOm+#)uwe<>E8*e0ev7$QdKZ4!OWZip^R?+F!oC^r@+ zam9L7&Tfg}#145JwN8a27EAfLO&mX29mD|xpcjk+KgUGMcXmwS+k;HlgBp<~f_=;b zHQ^FPF>Y@tW*|N)Y@q1OMvA_uLeUFTDH^=qsJu#w3OqnjrEU}*jvI+$6RWXvjDDw&dfI>-p)g>8!0$HcVGq1c($AP@$COdbM;GXbaDQn7{xHJ`^$JX3 z`#boE0VCWF96=izx{mN_@NJ@aCtwS8z}){+rMSJo=@TQs3oE1eckoFXW&nQJOiHve zgq?mnJRFcEa&R!EE=Mm5kD=6m9DtWsQnCqlzy*&f5uA`D(^g8R?UdrzfV*b~rDY-C z0DD;aLU=^~M!%a@MX7tOf&1x#A6-YOv-2o*tT&|;0)xMY9Z^0Tw;M~;Cp1F&=z+_? zYY9gV*X-Gpp#$%gI^;7v#GYrco3j2(r>r+Y@Edcb>{<4deNl(9zaFEU!|<{_A4U1Y z!0pu7Nk#d<+e195EE8IVN$K>(4*1>52>Le4fxcq~s5xFnKO9Y{)^0BSd|OWSyLQsA zC7$$qqd7G@SJNM7@SwqKZC`;r*PhPw*UAdDr+`_rkrkD;vQk$SRvz9TJe3FFsR-`) zaxJSxAP=DcHG;{EVAIDGCFpD4S{4!=J0nVNlk z-e5Aj6dmTP6Dy#>4&d9?*?g}U1qtZc_JVYehB?7B!s#5GC=1-+N_ znzdM>R^=pq4frmyEmw;-gKfn9=yBpw;W2Ua`&+So#6>Zu=Rq;Sv7;EY|C8tu*(%g3 z()n-Csa)Omf(tj~aOywXSejqVZ`Gghi_@d{shU4OJSk=0hAw>1>@a)HP3GHwmh-LA zh4B5tYhE2QVVRxNYQiQ=U12k?duwfQcI-|@M`~12d|X~=*|rt zN!oy$$Tb~cdE>w{0k$^$2+3Zy!xu%9WFN7+HLrlC2eYIqu!JeN+guMJ>1^C;cE#Xx zZ<75BT)?pp$$ts(I7h*61vPftlj2VXP<#t^yyv)UHueJl^B6d4Iq-V`N7KxP;(Q$` z9yoRUy*f%@USs{meDpnr<{Y}4 z)FEN;WpAhSIpZk(K_zr{x|B9M1AWeuQohh#^vzJ^okIbTZE$BdeY$G3^Nd17~* z4$o-03pBCF;Cj>ojJfb$A=v&H>HUU|GfuY;e%Cbg-+(2IH3lseu&*p?5wb>owr z3;48=jGg9<<@2sVd~rh$zWQz>yLry%+j$0j&-y7p=<$gkNBHowU^jko@CAqWH0D>| z2J-7exA^UviwCS$wseS z$)M+p&+h}~@XP%s9DMdHxTX8qf1nNftlG+V zR}A7iS_k>|3TSJ352d(c>ENVHq}ck+Bu#<7_0J-TS<;81A81ij-FAu+q4XiZmp&{q z#^W7*C@!Mt1(qZQ-YlC?2EGdJA^s8Y5wjurgstF{0QbIuU&l3p&jsx7(=?J9mLZop zgJR(^9lIBNmS3$T$6YV3a|OjY62&<}2X1F59vYXB zO^cs*0evxXJH^2RCN2;@MuS3upQHC3ZKcGWS19RULu48FQ}TZ{;H12vr13h)90D)1 zWG*E%L{VbsB1-x?hEg_frqm*Mh7Cr~dNB$&Pzy=}cA2UXf^1mS+drr;_*kSFBi~gW z`(92IrB-8(onAs|PUv~h;4`L$IW=ES8J(;sbA&)9^IXdO0zZi0P-xTTlxd954bS1% z$S!Ftp)Aba*>4U}t_eJkjFYKwmI)O#f%9_EmdYkgqw=LXRB^HoRc?x>uT8P^ZP84s zKGaIzv#ROG14H^*><^`si}(#H52@dx=f~)KaSLDq)BZqz|7O!SpoR0LNhc7 z@5i&!{x__=>MyH2d&a7JQ@N8_JF7KMV2!u`ap(7gSX-kbck`G99Knlw9k~S$v3z(# zU*JJYKVr_D%A*hbm~Se0bY%K2g-fr$>UvvilI9|9p%uCX})3h#Tzo3!ck56ZoFZbM}u}#*dmN1G6jQ z&_Q20toRCtx0iB6{S1CP&w?WzmUGO{-5ejroK~@e^X}+#dHqHHIq@|Aty(BLMIRB} zXa6S#lnfGMJD(7|dxTh}eO1`GtrsrqD#ZOSIwD#vS$x}QD(SNCp=3;Dnq=wXPm%-c z6D7_b(#KWIFv;nR)o`sr`;&(2YOx(YBj?I9F_*u*ddYw+mp8%fitZ@ao*+>SF#s+ za|?=_p-uAl6ER0Bk$jXk`d}T&N8^U_3EvwW5C4@~WaXiU^~DTnXN0?DEhWs?gbx|= zxw=*0uHt|i``^z;&F!%!`Eu-(r#iu7+JNGQgY$9;x6eV)1L=-}*BJOKa5PPNE~ljP z=z*Gnl;~nji8J8$w;Gu!U*Ts|SxqS`Z&7N)0ZO0u4t`JB;8}2F>9#dwq6=iL|PFWM+edHHK zncYrO#$z4Ia5kolB3IlxhlBTWiL&3?Qtm2C%HJDLg&y%#yxxXN(haF>3-G(u&) znWe-%&6By$)BfE5yeI2(_PD@uYu(`V!e{YYN89h~4y=8IqR<5|3>n|Rs#fUU-3yI=0mKa=3 zmza-#Cpnh#OmeeaQ4)06K=MvyrX;%C56PRcmn4DNm6B_Hk4W}!Q#PIGyGcp2pFo zSsXEWC5Oz_wDd=MTRcA58EGS0ThN4IK!&^*HRKGm>Q2&miCQhbk?oTnL=v$B7;O%S-PGukHf7~c8e-*_q zfc7q;iV`$yD4~a(;!D~o-UB-uUK?@sh7 zDM3e2!qgT@_~#rY*uuY|^B9U>3rx@10h$MQl1tQa_q+n$=Vr{fxXpl*5@!mmVF5Hj z{^xM(G=jd|fs(eu3xc47?TR_{H++uz2}-&SZMkC$rTke1E*P-Br||2$TSaLO*zF7s zKpRv5{p(ER=i$yd%^Er8$dHve(#I~yGgrj-N64`gg3k$0rF7k3a5#xF;=m;v89-Sf zQIuunOqrM8QO3(!${1Ts8U4`@?@p#ndwkvgF=hMZ;m&D<3}zkZ^T3;S16M^Gbq7z1 z@@=R)`wXf)1Mldm74+?r0{o7gsm98aYHg7X^gfS%sj5;Vyh?vp!RuiV@|1_DQd4ON zwVXGi*2H)8*BpK-m71(reusmmZ&_d@l+< z{E}#FNst(&oRMrDX)d{1rYw1`F+q}cZKUK&bhxC}I#^OEjg+K6iIzM+{aNDL!&b6Q zsa-N5URk2?%3Ty16p7~>BgBn9w}t)mr^4#%FClLA5TkWJiN1T1MCak|Lh%Z?V?~u* ze0(0K?J8&KvVSX}REDwV)-IIb0$+x$_VB)Qfv?qn-Q1lQtiuzPXQE{ld_Wl%|qD@lmYKlEmOR`k-uhOO9 zk}RV51E@8v`M|b!!c%rOB`#=(hx2wyG+ILmGR%XM?NEbTDV~583`DIMqTUu8Qv4(M zNvrr$!UzRQ7>c>j*p?Fh3#J73t(36c3_TFFxePP$$x4#HME%XmLC?f)2y9Qj7`0cp znc{V(QNm>_N_+*K`D%Et_{UIEhAt(2(uc{L+KXq)wJ-X zjL>w-EYYW|6*`pFL7y_mcA^ZCLm#8()5q_?7(xsw^T`;>atfpDf9;S-sY?0%;57zb zbg>oqV^tYccEE(nEj+1WqZU=#xl+|U@K%z2>HAb4swt_W+Qg+)R{))B4t#8FT$UlkqH#SswuJcO$7i?9m%Qh>Q^R`H3-qm3z?^|@14>t5=`wd3ykdVbr!I|tF z`G_x7tmkVZ8u+HiCid)qnY|yrV!y>7IZ$aN2c7fhkSiAaqVx%e`?>OK32?ia2RW+e z29~KiaMJ8l&VV+eNMS5j7AWzrB4?o#y;*4XIU{-w4-m7r9^hoQ1X4?z?mv3l{3|U&z{+FgtDaJ zbb%!0|B>|FaW(kg|7}Up7P6D6~muj-{o|Tog6pu55L-c6}R#8 z`EJxbcxx`<&`uV7x!RF~!{G5(f1VTvFwc!d4N!k*lMhhBkp2|Ed^W}Hn+~oZjHEYT zlGOMf#iU~9dNvR+&R>q1Nzr9^h6i*S##9Wz`>|HC1y&@mjk`9+ifMq2JCn5@hAmn zmvj&Hhp$=3k$eO0U9SRbF?L3#LNz7)*Gvi10&tTKtaBuK_cQEpNn0s#C%nU2MnQuK z?PfCYPAPc22icTt5Cp6eoIyYcr9A`J_oA6nFRnta5Oia&O5iJ2j;yZBloZrN$q|Gd z51e2DdYrxwW=j?Lybwyo?|o{*FiJbHhSGN+S7BEqyjOH76WC+s5b)}ITqymq9;I*4 zqVy*0d7T?bH7t%)A9W~eZ6sx1TMGTACFMreP~I(nDp(pyg*H0yvvs8sr^Qt2_7Yjk zOR2JFGkuHMP2ZOvrRrvLs=54&emVHi?*s5TDzBm6H@DO8Gss4`R7`d6q}2F8O3k%N ztgh?A8XXN-<8B&jTIjOo=f$jT3w>C`WY%qWpIZ)dVZFX-+^Sy>*56*pt-IE+;p&xa zvI;ln5^wI1F`K)@#B-0|KHO)+ejZ3^JanxckABmR9b4{U=UD|jH8hB4N0stIaNf%r zGkLYkHC_)MdGn*yyki}1fm-FTXY5Hnii~2fB4a+KeUQ&MsQ6r#zd{UZoG8ZA0c+%j#?eeCHd?v39l=WcqhEtZSMDxh6SHq zI@dlQEj0cm3w@K>!d&hxx*yXNcG4MQ?ADfI(c-aUUzd3zz+{vNPwgf0`&dcZ>{u=t zb-tTqSL|2GwGksENtur&kh3@%&#tA!R%F%2uqp7d*gA*jPRk7 zF0px{ZoH$2ogx!gBd&_$qpgMOxn*K<+pl7X?GMqlHeML7?I^S#x98eUTe!GwJZFTq z=lH%q`BeJEMZ@di$uSOkx`6|yV0SV@Hpms=7F&RMxcpBm=|l0h z+bQm4dx{$niLBRO6uWIV#fCxm{1e>eMse}i5nrah&+m`llPD=BGZ6eV87 z&r3zEJ5D9#9{9DKL@lP^^}%YCoK^*#6WB`+@aXxy@p@n}3Fv8iO({Jin$lC%D18s| zFExOV9>y%X0)FqB?v$|(`c1oF$_jZ++0Lsd8@kpH+VGZ+uYw2Je98^8rMy%R`aD}n zMSsB~T)IwW4t7-WB#6E#!QCxPqw1xl^uv4-{U|t2H5nQ7b2oHj0nm+|D5hUJuJjwY z=pQv-s>jaV)MgSj|0`v+2wzq|i|ol=POSN^FKe#@cV{kR-A$LcC9;6^wjAYFL2X&T z40X4&BOCgSXOqn@xUKnT?$D>pxE(Fl!eN`E&+e>_b^%RKpY8+wzpC zi99RsFE4o2gO^R%%BzkJ;&r!<@#c0(yklH2d*FTkkojdk*4Br;jkfbC{bD}TV>F*z zwiOwU9XMo+3EyZd=R4hR@k7Hnesb~(Kil_=U;Z+K7jp)`hkvKEJd9;?I&yOBv8)<9 zm-F@4bJ@n_{9FD*XpBE0TD80+%vLHy*T^5jcEx!iB=f}Fnj2!fqNO;s{e-w@k|{FV zPm;7KGms3Nc|x-GSh*y~?5yOS$16$c<#mo)nvIUFmRULK&g(7t~^w^(8cxV3%b`h5P7+0k}Ah)0a zy)FfOn>pdvR0X<^CbV^mn?`mi@B_C{{q;cpa{h%|!a~0Tg`Drm9VxqnlF}D_$ZRKk_|{Y6Woz`!nWU`4bE<#AN4f^O7R<`lNu>CS z-nhaCwF>T_p^6eip;4JIm6G6RofLhClKz6*D}W}Y9$4ir{JU~7cb>I}S9>HSz6I{m z0XMY3m=rg1Ns$gs4ED3c5AbOTvxK%9y)p@}xrrI|Ip)rXzbM@qIe5S;)7`Plwf4vE zR|`!iFqbKqPcOWrw8%nes)I=dzm=?5Q_4m^{$P$wya}!8!$B$Kj444rS2aAxvZr`F(i+=2cuS#zf)%YK#nowt|QTe0(%#nHC zfb3-{{rOWx4L)x4cV%n(_oNe-x!(1cZT)hhh3cb$;&_d>`N8Dy8MUVqHjizJHoMX+gVoel9R`na%TMl z&a?OAQuQ_bGuln4mE9CA9iIu4UOw<9`Xu_M9228&nuzI%SHwn#i{gaKFA@5qmx!Oa zQZ%G2k#uznmrQHwD>){aE4iKGEm73yOG=jeNNOG#Nxp45C&}KhRr2DAkt9fMh{Ub( zmt9#J~{pLo4mT?AiWD~?P!Ay#Y2g!6|MVzAjF(e;U@Fg|--XeTSU)?gtQ zJzb33z_}dP<0ZcY=kd7rD!%Qa#i7@{_>!3h2f;%lAWoN02RFc{K?!V83Xk-;l%Q6C zThl zz2=~X-bPW9e>ONT;1XfbY#EnQa^y@(nixWfmYqmB6gcIv=cKSf-?IY_vlIHSV;4v< z86Wj@F!ySaycRWB3cT|R`k#m-rTS}97Aq<7HZaL1)Gzd)$~TxjH48|QfjaCvos@_C zNqHK)fqMxlcj7hg?I}?Y9Ntj$%cZERdRt0%$fFc^V5cNOv!VrTQ|S&~p)Vz_M6Y{? zJ@PYV*<}fsbMKLICa_Q&NA&3`N~&@Ie~$mpk=K;=C703@eo=-7dfM~Zlx~Td8|y`> z!!0Of9_CWxIA~@pDXp6WrGIpUh9!uy7l1)jVT;*CW>#k{@^<^a`>T*2|tPH%Fi_`_*L0(e!JI`qd#kM+&q1jJ5J@~UVS;! zX&UFg>&Ras9l1L6Bmd2E5jqMtVQ7^hIt)%1Hobm|p;6z(#IBEVTlZdgnqLz^uAjxT z(<4M-!7xdi1tpT92J0lNug{j88dfT~{q>V1YDS_Y{&qV_jH{45aTp-E_;R}BVE8Y| z^bTz#J---8bZphd2UB|y{sMR8Iw_nlzPZ5XOfU19{$6}45q$J4>_(d4x-W)N z!m#xeU(<==qunU}wlBqBMg6_1gNAY|e4SC7h8pmg4TYb(J4s(b(>eS&Nf$Pe^eZ@r z^Kle+E}9bl$GO4BC&|tb+TsJiFPbQ&p*;%#ek%Nj5!$WGjx7yzgt&C;DH?M${Z;z?&MB*#Cc@=a4cg z13O(f=F#4y3=e}xJ8pz@P_LK^719;hC*R@EZulDdo*j7c!+w;U1D$A+^u2#4`W`8#>Q!F!LnoVl1UXR6 zS;X7k!M+}`dYcT8T+T|b7f^)3(Y^XUo?tcGUgzm+^j zBbdj^nt75}8PCvo&2xW!;l0m*w66 zaniNdoYA>6=fw2m!q`4sdH)O7y?i4yZx0vxD+USkC)v0aNE8EGR0wgootS%hpxAo# zj5s;3Ufk;NRLCbC5)HlFB^{b}NvPaip6f4y*0wS61qb(orhk00vtm9B;%m?_0 zS#Wd^CH5=;eu~{O>;NeT;``hM2VpmY!D-8Ecy>~tP}RY=}Jn`Ev3|U_b6?)F|vektFV11WvL+x-2%AEtw;31#1Z)= z8T8QvJ`omql>Z`%3SZUImye}XnwmisL&2Fx2hz6{)9IV%JNg!Wld5Kh(syx(sxkZj z7==3p8_b$NgXw1|{54O6Q>~c|)%{mUjT+sdGKBkMVbaI2D5Y_PkD+t|9Y5zS(g@U3j_SYhy0EC7(7Y5 zs^75h@pkO*+J^(f;axtBZ8}^!Cb-%(<1^;pM+Tk3V zy^UoXA9A9wuQ)emy9m#GDzet}m1xLXN;+O($%u*il9>^qk~P*ciJRwb$$BwavdC}; zxPyg~ehZ&VOotB?jq2VaqnnF}Sga;41auZhb<4%Nn+h?_J5!8o-XMDS+acP2`zQ=T z4+?eU+I|o2%%7Sba?%|;e!n?`Bd4$Ddq3XrjZRD886C~%wGVT^JWuvp1;1?F&(Nv3 zlB^&2uUtELBgbn>G|(A5GDP z0Tg3;m|~723pVgN#T|y{^gv+#m6+kQvD1ASK}m<;Yw6I09?}5Z1-(qOHOU7@kUVuG zDb9@|WiJa-Zo)pbFA990FKR0ocqQHonxL_k|DyO6xLx?)zhjI($rX8|)J3f(pyv85 zr^FfPqhq5fu{HYKE7aaRcg&p;;Q#P*ZkghaF9rMtbY*YLNxm0-EI}9ZYJc=M4`|Bl z(97_6hVOF{-=hO?)6bnKQB6*X+V~k#0gMK7<}=*O9y6uX3=7I=uS1!ahfyXxcQdUr zP*Z8}{KMD4zoab2Y~2=l=u;K~14S)L{--5dMd@=7P^MNVQaywhpqd9|7j~l$*X$|B zqlrGgYNk)8UsHkJFe>~DPch94RQjxz%3l^zMX3{2j%`hqn3F5#ETnHH$m+s-<@d+o zRQ(&b=SI2oQwtgAjqdb&m^syc^+IMs5H)uBpMRzU{d)zim^h1@S2JXsKV(z1NHCRTH1pRQ_I<8z!Wws>BkoJHEg-cp1TYh!B#%|xYwE8JfQ1# z9%8D-Bb^R2-}=GhYqQuz{VLB;+Vi~dnY^TN46k~j#v4k@c-uu&-uEJ!J zNr!u$l2*eK#qVy@MCSWq@j~8Dgc|JpAqJlgkgo0aDFby)EX$Etc2q0aQm|%kEE%%2fDrnUZairdX_-4iT2QyVQ>71_korAB*XoKJT!;kzj^z%OEU-RVV%u~O(e-%*kT?-eC?C{eMUk`B+Ol)jiTcMHmh-$|+=cuXEg zna=kpJrmk7Pbub1^tahLlzJ?KQuA@M{?C?4;z7H zF5gWbC*U4^qbcR}R#L&KS}NL{Kqce6sPuL>DxZ0iDn>a`g?oSK#XP8Del}HhMBl@V z{Jn1_RX-U_H72+VT4GASFdNq{bEP^f)80#1vVQtfi2pe0#@6xly{oq5rDYhDp?kJlSo@Ro=e-t$Po2a#2I zDD#UOsFS-{VWHuLLagZb^3 zQShaF1Fy_FmOVJniC6D&+Wg*}_5C>K-R{jLs@43%DW99y<%t$Ou;=X!6CKtL5H@B* zgnb(7V~0X4s>>HUTRj#&lm3d3`B~!GWj&GduAe9?FGBr2le7pllIWHAN?LAemT0C- z6}8(W;&aVHq2xLdnQ0}inQjrMck~bk6;)!5;+2@*YNT+ulrQ?_jTD{dwGhU$I|-e* zXs&DQ$|ZO|P#r{G$-FpzJsE!TV{G~MlY9}wLm-Zm%D3u;L= zMT-*RFlR1+{!!NwT*6I??+KjpFYbBfCQy9wJ@gxJ1%3T-&r?a#lTwN~+dwhKY83qk zbEXxz^oRu1<7Mcc-Eq?sPO`tKDHH5fC zr9^1Blz-%;eBeXMZc1q13`w4`isUPtNv`_<_X{^svvu%}C?GkZ&pria^V1fXDfYjg z=y4a(GtHtX@p>U8?Fgde$=Lrw+$h<@pOUwZf~E|&3*+-BU96!@rzBE&4kp!}P|Dmg z8+j$bRtz#IEeIOU`!$r_9vJ7=sg%C-9em!AY0|lz(o1otu&#!(Y&uc)%RI_an@TzV z{ODt5F6HLKW5vIg3clA-(M$N(&U!?p@B%DL7F2$91(ko1Qn|8;%Cm4A*@ht3C66sX9F8tmU5eKdTc!UGMir6&us@!tVw;2V9Lt>+%& zK8H{9K;6k~AGL)?ojJu~p+7rmRq^Dkt31;|$qR-i^YSgOyf*FxZ$?hnE(0Atu(h5K zkICd?B^}s1XbqnVxyS)#-#Dmz4PPqm$)O4T`PS_5eD`!BKfExRpZNUcXG>S{%c>%d z>Z9WK*3K+#wT$Bj{9(B)o|Db;Im66^Km63@&#ez|xsxu}ng<9C=LJHqaFH;Lg?Ask zjQW0=AVya86qAE*iDmH9+?&TJ7$WYwtHkAkXTtBv3~^|yuGlzWfS7ygyBHgQUT4rkSi5P9b}RP@gF{Y2 zBfmY@Tzv|k_{6S$$wBAgOh9Hw}C=v(ScDSp{tO5h}t z{r8CE&{!x6(0j5l>#Ya&xDvG!j=j$p{N5To=TQhA;5&LIfG?;52C74f+g_v`wHb)(E@Q%U8uom49g zDf4&|Wn6#`Q!kCu>+sPISc^AwVNok616+Itu%!&_cp0~CD6rV4FnSgdsE z+f6U}z6E*ACqwCH9;4?cdQ~ZGAi2wJo`kWnRxhBgGuYgF7H)#Zvt`!??wlRPR+69G zYq%cwKOD+~)t~V28&7%6!Xh4Pb)6@UI?mHRUgfzT4)c=VgL$>tJKi|ejko0-;eFmU z?CEofj|vy|YX6CSXFX#7S_PlWtL9*(246h{El2T6zN6j14>S|`@waS#`fM)07!ks+ zm#^ZtgFX0t?jV*%4CDBJjjX7O=afzMoOuSBSeZs#tfS6VxBa=X;E~XdYbRQRN3aMU zD!OMq5w>!5F=pRLF(v=KSe~0Mb|iNf#|A$V{*Sc8r4`-b@lYY|PmUIk6nn*^S25!L ze|F;5x)^cAb%!|nXNK^8ohc5wMT?CWHj4#=zlaG>CX3-otwf*5S)xQGMFz#o*1!v_3nknxp#K-L}ZKcG%=wY5wq`*EU|ImfxW0cq_@v~Z@&l!Se&+bdI zpvxqS3CEog>TVk#dJP`FW=YKvbHl%8UZ1WYN zr0SkRnLfDhv8tm?%cYb#*A;r`Jj#4Sl=*flW$uJ#(^8KzexWbUt|8S#)SWwUmHUU0 z#e9KsK7qTteVKBP9-vRZ?oodIdMX@VMaBK#Z+kYJN}}}WYYH@DFLSAEULlobR#Evr zQ>xe*O_hpDa11(BEi{<>JC3#9$FO!DbeiqI0biNTdXc_t(6Jr2IrE5(3Yyro1#a=Wv|@|t(cHc~g*){f z&)xcTXY2TK?vvoa19f_{{h>ZQYUENDgDu&~XBba$8OpN^AaTKr$dFm}yf$L@Vw z@UDlq`M^0dKDt9Z^n$D@WHgVSA-JIupnM+4J=3k>7h5CR- z(bD3gFiMFN9nSw1)=yas>Klur#%W>-yxNyMek$D5%f$ZAFT}B)EyT&C--N%-R&mzQ zTbzxF69I!`gx}Gh!n@riad>LH*me4#SX&AF((I{lzA#jba!D2a>o<#T`T3&lz0<;g zNobx^^6za5{$jO?Ro!lL0&Z2z$+{S-bRz)vFSqb8o)s}0CQ>koZ<#LP+SwV zm19x!K^Y{!5dw`UaQ~TFz%qJMVvkbP;9^q5fCub=J?-lqlK-9!F0Yy7FUI|UUR^N_ zc*g_>N*oUyvw0_cjhv8w5JyS+?J3FJf|4`}DG|Q6O6a{6;0oo7!L7FdCsB|8>z4!A z_aaDUQ%y2Lztvqz3cOyqa}_erfdMUFiVVEzlycRN(pm(A6VF9P!cNMZ6G)lG?xY&9 zmsCS>U+^y%StY2i)S0B}RO{=f_>XoRP&M-3HSbf@A7+@Jr5rmvR8RH|7^rAf=F z>@{KTdce7i z|EkZLE4#9m&t}$sb&y-M+{C(}4XhW|$ojuFahoYC;VX8DO>SRfvnwsQ?dd{pm!e?H zd)>GTydSLA>u@j4FWj$wDG!>rh=+GL!D9wR^4Ke>Jjvx2Pj6np^Fr40(m`>&s^%we zGz;Wyn}_n=bQ|^@b&QX0F=g*|zU-$i<$#i6KJSytm-2V>)hau_ksraglhpY>Zd)H; z@#CjcHTe0p&HU=H4M!Pm=64}SIA$MoAW_Iso>t9C6Gw9TxX1jVM4t<<=kT{RI$Zy+ zqtH@cCR$0q3)7XmM91ba!e;kxG3fPqG3Lk?F){j>m{Ybu{CE6{*zjE@w*Qg{kItjT zK@X+y+-r>;uAA`4st`MR&J!D~Pl{#9W5g_{c=h%T$cG|Mkav{I)d&`efuk-Eg#vJ(~Y9jbVzDlK(TIl6bB7tyxw$*-&9TU0m~_V{Wyx(gwEwSd?Xs7qkat? zjJ`egMD*3CH%YGXlH^Hgq*$FmN)ya_k-%WmvPn4~yWxX1;LEdtO9p@ssK#FBPjWB3 z=5T*fw6lR8%oAG5m%u-uWp;-qr62eJ^o*n~;O7V7_UL8||1N%aLD4&xIjYrhs{okRBdc74i%21w-}PO9;~q#9#R zs-@3K6}+9YpnJ}C|3x3N7SqQL$c621MW1HNDeoEd(QTm@>sdpe|2&|=dN(Q_sYPGb zW>Co*C4Jq9{Gey^seGazRWxp-Z|~>R_p1x($Bq8<)8_&G?h=JN1zq~H>nPPFAt&Kc z2{n~k(?5GVR(n~*>faBsW%^*)>%^LXbg{bejaYHNmstG!q?j`=Uby(25{}8=#fZ>>!dB8wSQ~8- zmYp^W<7aI}%hE?e{hBTR%$&tvCe7rmJMCG~FM{70X>#QLI)1Rzn{WAj=d131e6eB& z2iAXL|92bt)TW#4Q}~;`p8ZCq4)&h^bVyeCoMdI#%gn&R`D3?B4kl?AHW{+ zxHosF_i?54z8H`6V0sUK)#w!-6oZ>O>0<1C^P4En9*B zfVVB`&eNV^e`Hhq4BQOqJCS_aYiOL4PO(ejW8d&Kzo9EdDB1&&j-LJTa#jh z9m!*_ll;E}B;V|cyFLq2b}s>cw~CUwub`x%*bTG54_?ur#CYH=i8a{u(8vDA6CNrg znWGaW9F$XnT`?tC+fafPI1=H7*&1K_n}K=Mof3D!x4le{k`thN9ti#|#FtVJ;#MdL z99^LoWh}l<86GO^YT=ZrgUsb)?McbT2rP-wuWdVQ5ikwSXpF$}s-i5NZ9H*>q zUZg7aA=OQA2(vGf%G!=p0UDI`RE<73JfNJ{*Xd)Els?%{r@W&fl>a=C3jFiv^GH1^ zYzJ?@UeHQg;10bJwbu@r=vPyy?AR}=*k4NDmgvxTi%_bDHoRuS2>AO|((iV~RJ$A5 zKt{Ml{}n)umCp3H={c))VpiW~%o?SmSZjk1Yk#xGem5Qbc?9d7AI$o5&U0%z&xXTl z*|?n>o5Z$Yvt{qOtwSWY3qH=4@Br)l@+NoRv5tG@jpBYf+u^yA%)=gyVTXvG?3i$s zoxiy8G~?Dh$K^LK%ChGb?+^1jsgm6*Qh0ZGcRm<#vrUM$3|~#n{Zw!r@(`u#Z0>`b&^&vM)f=PKXQ4t9SugeG23#>jmp|3adY$fdQe@fyJL!bm1MqE| z2|W9J+?)dk8HGC9^&ImjIEuEYyLNiGD>tRYTZ1WaVhZ*;AMkR+C~;6AYWMvZ?Sv6?@My{;wn4A|EM!?OJ!?HdAXcYD%Ma+Bl?|( z4W+FEE~&o|m?bosUzSsPpK+A_D}*xYfU6{}A=T;}QbjjVmd;?xnp%yX*Nw6~3@K}) zpsYQpv2EFu)!7!ecN?)A;^X)L%92D-_KV|`W8Xj@Pk>AKs*k+9Zj>KtO$7m^^m&mL z6{5!%HZG+iynhwDw5Bhsb&=JDoUWKvRDLsrD%ZhxMZFqce}zJ4X7L(D>IIzNn>KL}PU+RhrdN70%&0bD{1xA5M;Es>L;w+=ksH%nle_1tFh zTsG2r%*Kb8Vb6QbX7LZW?bn;!e(59bILv~(++M?0mH)W6wk{6{{Ki9OJ>!wBwlfTQ zc%0W=p5(Tbr+Y8sxqnabl2^67>bNy;2nyhB8;Ct-x$~i46Zm*+XFj=N51)Q;m(Skv z=kq(y@TDcTeARUl-w2${w{sTr1H%>kxY?DT?y=$Leh&O?c~T*e(qF92IT54-y?4GlkW)A)>eJ zwy<5aObkK24d47(jQTQ2jJY&W@R(DA{~IaBJend#P535;?&~84-aapS*B6QIx7rCy z_2I&F_z9tZ?SRl;Q_X)(?YY`uIu{+w=B&k9tZWHyhcx2nxuN{%=X}1SS;E)y)cNvu zT|WQd0G~a6ko^s$d}`Dg_DR3VUfxO2L_31Nb|l4ELvU_Uzz$t00q>u2x$t^e4KIgw zp8t1mey@_Ew_vVZFoL3ez#9mGewakkr&hSbOTj&m4aK*=NeN@Xx#5N=VOlxG7g|$% z7<6Y7!AV@74gECy`<9_5*BDa5b9a)d)&QgQB!wiB6n@~^eNiim3`sHChZGzKuLtxZ z6TBy&{^a)H?nc?-PVY4-TpUT+Gn|y)j#HwuIVE;2A>~JWUc8tR4JHB8l#(L5J;|Y^ zl66P_egR*XlP(lnnnSUl$5EWS7BZgE@A3|l{1Rr|Nc6f-;JPnD*Sy*Sen-%h4(mdx zL8BK)A5mCoE^lZtiYfU6k5TWtM4*00&c ztvAPWo6I9@WY?OF;dyJiyoSxE4Q7kAuetrlcHBw(1$XOG$UWO>z`uPs+vyGFp?gO2 zsM2f}{|(^rf4lPJ&yhS6cQEsx?&W1u?Rm}22zIme;a%{9K4^QLkKjGPYwAY!-J8e$ zn`dy~TsdD@x{fdVuI13o!F+4r9KO4EGCw@Llb={m;7GSO{9^Aw%$tij>Q85WFBKd+ zxR7P1tvKoaAkLWilykNZmddm`6LG2T_@}&nu!6s%|sth;4(KRiY}Nr+Xuie zGv<)cPv0+eICOTMA?%80_Fn zT8B9(cnJq&`|{~~GWM-M%|0i7v6mfiJ`Z4o%c4mUjy}^;O0rlt=vA&$yh9**6SxD* zNhF<_K{44QC?=pc#X$cowdzf=y15kl5FXR=N{THGf;T&IMQp%zuf+`M8wf8HKg@Df z$i&0<+OeGCR5=v4+>7FNU=HoH6udlot;Jx}eF@3mfM=`9AbHeulJD^X@3x)f?%=rH zmy_H!k7UJKB&*OT`7G>smAGx)yA)i3KKO=vq$~$F9}kV@UErI^m{H52_u8`?nCA$1 zZ=&C|eFV>HH;P+}o294l?@R!f;fGpGvLo37)Vn=;rfVQ2UvZ(-G2ji}+@#d?3n|rp zH+&$PDQ!4-xd8!`y3vkO@*F93x;v$TlgL;G-hA0?$_xWu>D`(#5BX8%E%0=CVc6$@ zqohqj&qH3qi|g?4Loe(%3O9JIDGT=wSq)t%J9iT5ZZG9LaiLrj8~Su-1m%xMmgPqc zDqOyXimu(GV$U%8Vy#PG{uWcozhY#T6jJHtKq}L^M`a1lB(ei;J|&lCBlCP4Pg$YOv)=9Jh2vBBKWATF*Y+xJc{z&rzTd^3s&{-0+9~f| zE%}ti3qI5S5}zB3+ceypU#V2`^?4sSEbJEFd)A#F?Ll6G)-HZFbr-*!zL?*1Y~pwD z1dNF%PUz{%%9=}@`ezbnP5s87j(+CisJ>iTLi{_wGyjXM5!%{kgq~xbXtQX5Fzw62 zBG5@#UOOwgjJznivrP23(^d35wnNy2)C-$%ndrH#QCR)EExMk|7af;h6&4$Y3*%^s z(7*LX=xl2eYMbuzZ?T%oXB^@DqGOyH9ma|ZLymq5&rAm^4i9_-uUbF!?Q9OQhhL#D zGRIDg=76qe__SRF`}*x*AM_+k32)HGBnKNpu3pn*s=vtPP0`rH)%W)FPHo8zkcp-9M zG1ncorMQ@l;2DtD)$us|#GtwC2@VfBPFXusl0CC0S;A{%p&R3#;VAqjuxtK@-aBO@ z$=X2QVt9{a7ad5xy%zUEzesVll$0&d^OoUp656tp2S|AvkGwci768XwrUCugR+2~7 zk}M4MKh2-wb#+kp`2U2#LnZ`28~HnOryA&1yeMg)7BtN}QFGwOOYTwXqBKhFgudrh z0Pksho{w7tOJ_=XY>v8vFGMD|x^)5I&!Dw;mj1W}HgHGMQqq1*vM$S4V-{EjoJ;9Ue2ZhA*W z1+VFg33Ow&y@9D{Q0blkDlK-S(v0;~`fxXu*7~6KJgL0egDMx=QI+;<`u?$rekhO) zdm)H^>%#wNL=OGQ>_iP-fz<4MlhwU*S@S|`*6uTsb(U}87Kd80Zd48HStN0*4IfzF z_Z{vN=5y;u$Jx;67#ltB$tK6ovDt<;$fQi>_Cq&t$6IH(YpxwzpB%$|mNc>L;3+)B zWCD*c$mcQk8`&|+ft{b$^3;EV=bY)xi!aXRRn&(!db;wCwwrnXx!ZiG(2S2M1$#ff z#;2kUI6!MK2Q9wE!Ld&8J37cWwg>X8##z#raP!MA%v@wL_W`SO2_e8J>3 z2X6G?GqY;h&ujDDIE_Zd(s{?XGu!MV&I|J4fCQ%u?9HI zJ@EEBjVZwqb1(3Y1ZV7)3w*)b%_fC^10}XZhJqfjl;(GoW;Tw}G*)5OM4fe2QR>=C zO1*{pTjWnEvG7}YUWFbAZJ5z?%9t5V8C5so;b29X4ye018z~cawV7v}NoD$rRF5xE zmNxLrdKXfq7E+epBg!gyP9L0RQ_iJd^l_FCee^y+A5+38*Bc&3x6CQ8c@-5L38BJH zzEl+GN?-IM_Pt(+hWb5x7OK3qVd+G}Ui0rN4n|SlzIRHPxD-k#=S6-M?7pOcA%pJjX5VZn7Ty z8d{yZ!}|V8Hn?(uTYo_A`Qp25+;ah&b{@&*E;?*cUdEQ4Gq`ibI<_*K&o+}AxvzgW zwr!fsLmt-gh*#!JL*zWJhaOMbZNxLWkKy^&>v-A8IlOj$N8Y^R8t>Xr!v`*(;=`+_ z^6?Qp*~j?@`<-^>fa=+Ne&%$(^l%(sElS`Ue^h)&?*~8VxPTu+)A=;0gkQAp!LL6% z@_Q{M$FB2YS!xeXvOdIVcM>=&;|za#oWsQvXLH5TVE&05h5BY|q1`)0=tiy+ z`lf?Kn`tUxJZ7XY4eu_@&pr`ti~0)-zs2Zno}%r1Q(?B|gD`&ZR3sG)#!a^pD7S%c+W&T{m`R{Sy{7g`%lzLzkTZ-#mBRmTq;eEuZ| zg&yX!Q*zng&6Q8Z`0~j?0qpfk&c|n%k@5~W>lv8ce62`68@P%AbeaQzJ7lLpWAca+ zwgK~(Vs8s?fSv`lu-pUBX-~4WP9$%Gex!6H+0zh`_g)BoEf4qNmhdS`g0I+AlKJ@q zKbcMmql(cVajVyaIj`jflAb`;!YkaYuZg5MYZvh81<2e(A3Zmh;+{RA_}0Kdc7V?~ zJe%UDt0;b-GkP$3p|1zY1EB93;EVT*NKzcuB}FLy{2MsRF6?;l8dF9#kP>}SxqKD$ zm-v4b){^`a=G0=$(0B3PGPesUW_SX_nU5MhN{Mx-yJz6Yr|+aRWArolU8F9xqg347 zr@BCIIieI9=(wrdHI7oCsYz{y4oqT68Lg*M#{4?Ucn!~@_2Bf>$C0WJyp1g9h2NcrG!(#U?qLN zbDW9{;p=zBhDz33P-%Y;Dw_iSZslGoO$(yZQGKa&UK5oKg#XbfDOH#srHVW$ebY{( zs+jdu{UVrZwk)KdzqIIgx&_rmU8km~P*&Rvt)^u@Yr5F6mh%PHUi}K1v0B#EU5Hy) zSJvA)hFf|3$NC35vcb8F+`3K=e$vC)IC34EHk{$MSEg|Lca_{}f(Li~ewur1X$wxz zi~D}=$^*w$vAu&nkCYB$jtgL?ewyr36wR}e|M0>uH+jVfBVPAvDsP!dygSLC54M@d zhvQc8@whvDQtJ?(Ubu$OK3v4-tF!sC*>(=KU(Gi+KIS_QTk(VVlN|ouiz8o8Vy6S}Cga-BJ+6fWn?;!gbWz#0DhqKZpbpXKi}i@CO+6*om*5bE2`2`%^c zqQ#m-p*N>V80h7SHqL6ousKl}cON2*@1WM)dJ3bZ>qMI)wL<@UwrKhIv(QdCFVt6W z=lVsR`1?J7E-~rHxnEy$#^$H2^xna-9TxDL#((_G_$5D@!+g7`j;}j9@Ri}nx%;{e zTuB`V43qHbo$z%^4r8CuOZh}}A|D%jmy$=}7N$FT#igSpFUy68Eqc#GqJ$eu(XR|i zmW&;XF|*lvkt`ZI4}WMSv#^78MPIY7`oI0GUkdO>)Wng!B=3iQg4f7osJo&S(8KtV zOzQwL5Kvo}n<#dY1xa0QQcRN;N$q^#n`BL~+VGN?x*qv>@Q!$ANpXW4P=7apJz{UX zg}L-L=HCBt078%c=M^Y-N3YZC55BvB6f0VjB0d;3=0?g#=zFj6h{CQ{KbsPJp}w*) zhZ;MQ0(gVsz8t%sJtf-W=3vu$N`#&==?J)k3iP?3ikfix=50;i zx24gKsnD{t$GkZ?gX$Jb;r|du|Bj4<=h0tQum8mwMX9XSVg_r^aAchu%W)5Tkz00L z&3Z#|4?Ach>km4>21oR{&1e@klFVU~Q^ssQI)&RUyTO*vo^a>la_;sfhkIPMWt*Sx zxSxI%ZuF!)^u%Ev{a`OU+N@#clr)}p^c2sv58=hl!+B+O3U9Fd!CS}o<~{Q?`QRie z9~spWx#u-}@<9Th{-Vx-db{{SuN{1O(Nhk6=+3uvb@}d84}N&fg(Hkd@bl1C{AS=p ze*bD2$Ju;kxsQ~St&VU8=Ftx&E?l5KnM)=<;7TtQIK316r)E9y$Hzk5bb-(euM;}+ z?1gUP3Za*_R_M<=Ck&S0mM5*BFu?4ozb#4V1)dkWx@AJ^%O{~e`Uf|R*~>q6*m3D* z+#nq|&Z^p6PS&pEc>j+4?)+YUQL}-c9305^9k%nW1%@1|oyM1@2|hpFkIy#$;WJ$= z*w6h3pN#cmZ>ueQ{LE86@&i5uXPu#$pGnD&!r@^u7(8=0$r{kF3c)vT!VEeNzu$2# z=qtE=GY02yLk+q{;2?uge;*o1_5^iSqDyikbLg+$k!&?;b=+c--H0N2M+cHiMu8Vh zgO3Dy=*tA$s^f09^J{Q^x+FE!BWbrgB;7inr2mhkF9C}|@!qZ^AxRPv5|TY#vW4?z zxwzTMPDu8I>>)|Yz9c03mOUhUXm6UPwC|--Nm7I)`QG3Ed+sw2u9?C!XU;kAb_%&W z4lT_6eEgWsuD^9HQHBz|L#Ch?LaJ5y$fQ9tI-IdZG1 zq9nOrY`J8a45ud3Lvp6E&(%6fwlD8< z-R4cMp(@GkwqNobeI!4lObYCSrSM~a`LKZ5etb^J_!=php^_54=Tf52AD>>7(kNT` z801C$rl*wM$(2uDN7?gSq#`w(eg)pAymp72UYXSJzu!L5Qa_q^U^Ut@d*wBAkM=_2 zA@{u>U3jk~5?b3gqEXNya_Qt3f|&zxkG@!~3uqSl7tIwu&^Kxe14SRS&Mkn^4|}u^ zvVlqSAeg$|L)VS-(4848eRLD>&y^?)R=Gm{J%%v}BQasLCZ-)*irFa*V2^S*a`#&E z;uBmvZe#o9K_Ik-L-yG;UYl;CJ>`;()~aqc4YKlW)D){w zd(Z%1Lo!jeF98MJUn1i}Q@pFBM%&pNVblBIJ>e}KML|3}=59H?I^5;PDka96yI7?*b!MC^GY?9P zm!>Gj)QBQ1Toez9o$#|cI*T~Wql3(AKP(ZsrzK(ne{5~cyL%N9;aemTc|Rnw?I4L9 zlgbPi&ZNb960ypU*r^kr$9@>8BT;IbsQGk}n0F4MNG+Avi|0i-kF)aE>k>DLoZe<@ zi3@TQl?HoRXZjoJawYClf~Xwnd0#|K()uOujWAc`JbmF!T}1VFs>BayBk|KFOZ+Tm zA9+|x{158HyelN(BRzCo$FdKy-YnB4$@ID;y|S02b1NmOzn3KKqISjL4S74}6%`MW z^m^U^iqmCgG(CE1%ouY@mgLRc`8tp{zcNPBKV6nAi&>I=Y%1?g{*dgYMv`q%DcOTh zOLp8t$qCAp+)87~-@rR1dNxvcdXp4=GomLiTZ)fVOY!bvDYkKuV&_sRp=YA>khOfg z;~-_!7kz4UOFmWemdU(S`J!VXmGKAV>oXgP+K5$6a^4NEi}VhF7@mT0u0H?(bkqp@s9ld}_`Gj#`VQ_hFpgW(Eo9@t_129Xw!Vm;r0@4>qToVQfSOCcb=t>ArI@$L|~L z(*m)q^9HQ(o(z|*;c)GL9PY$6J$w05JADC%Tl|OPX3yX|uP6L&Y{rF#7jfmxF$8RF zg}a_Vm<02rA7cP1ig+9S4M9um%EA@yDavdfpDp!7aU=f~r7iyBn5jzIN0 zP1HQhL|x%I8?^&hY&0Af+B7KoWYe(uZJUOLVK!PrwQU-{A8Df*0vnB#!!~LY6Ht5V zIljH>`@c^L6uCD=HnZYW^j;(GqB$bnhT}E$bI<%;cck3!~MDk`MId?`Ycq_`Tl;`V+_LL2R@_B0qB1 zbG|1LFWCFq1~W&HnG(^&Vw|=~X>keL^Lve7z)1Opv5dVkxJ{6MT6t87?7`VP7q2 zKiU6?)h0(UOU%e!k}XOlrMbJLDvTtfL8N3>1WH!XOUe2WELmHaIVl%hN2RD&ogVXZi%X#^5ELlE0ww2=B#Zo%JlYXHt@~NGl zl=t(K&(pP};we3`|7uB9G3QMBhrfsWNX-m4sSVeZI+v&NXY)a+cjXQA@6=3xai@MP z3k@`XL9_2oG@MZdtx5l&ky#eBP5UwL?-H6)C#_T30nOX|hZdghFi>9&!wKuqW@S4V zxvzk6dJH>$8qDfti3Gwz2xn36sgQt~G5z=Tl!c6ZX+T97N=~YPfK7-7cU6Fr$A3j7Eqpa^&d@dS^s)^>P&L4}K zTgj-iYmdJ>ZEe&wC);S~CfjHvPPb{$Cd5YLk-Lrhf#vvX*9Nup5LBO{_VUwr6bE-k z-s}-bZ?=znqz+=T{zF*f?|A7_Lf?Td0>|CPt*|?|cG?OTgTBJwxGPT8*u%%^3XaX~ zjl(B@;6N?+I)@GLeEbJ{;%>q1#$#wlML?ai*EfG=rY?$|({7a?WEX!$q}q zrKpmxit4GJ#E~;r#`C>?pLLYVIdqH-ecWRurUNmTrP>npk^N4am}H10XG_k1O`C8= zb&%KxHBla2DM-==W(NO5&w)M}j4e`C4(svqaS>mp;0c%w}MH zbzwfk8hXM%Q)6PpY=#L|^r8C7yJ|IRQ|KfAzzl~8V?JuchpR;5ZUX<)s9VF)(bt_i4 zB%gIvU_>wMg+5ZmUHHRIWBIW1x)fyuN>PHyhj-hgF&QRgM1kYcTjqA%^ot`lw0kF)rjCCe2%j>0e4QXW=8*t3QF`tQxFY za~du&MsV#?fZeK9*n4XQyf0bc(9;1p_VXirmoLQGPNulnt`n|7aBGu0?gutNP~C1k zpV=0#BOV}PqXuH#7vNoVd!!W|M9xV&6dYfR;^(hXX4MIwSJt8Obrh=mMB(R(0@QX* zM;*Uz{gM&*o3_VBt=kmx;kEeFK?A?Y=T|GA;!AgPl!lGR`=V{gEO$e)`dh@AM<`YafoBgB1_wPYb<+AR+mC>h0 z9K)kr6jcV)xoENPE#Uj#Lt_46&F!*bMho$jK9fb^swT0|$)(NH`@ejk*ol?QT{umC z?~N#YiPQW!&#z-9N=<&vvE=Q4RYl=&cf`+9+M^Rlu)b5J?@f)#8BzsSn zlsohosPna(&NHKyb*LRI$!CluRcD)|T~bQ=Cgx-IZX;>zaVdGd$T65pGBq*Dclp{2 z#k|4QMAA%lV=+wwRt{L=Y3V`yV}X$i_f99qy^O8j)l68CDc86huPKz4JHt4ai~GV zLvx@N%U!Qo9U4y>fhK|F(7_HgdtZj;u8q;6Up!j&=#5re($L0yBHGcv*C8bjrrCec zZS+(0Oq&4}&pq@O1zryLkog3qC0p_7C+~MPb3#>_5vu1k#t-js)a>=e zuisx#yZSGF9Ztf}NyqSwe!nk{t|)7&j>2C_$l3oFsXwieFk?AlZaE-K>n~m&-j3iI z_Yt_XA8wUA#I@p5-nZKa|3`Ch+V&TGkc6Y>Z{yJDW!TSupMA{W^>A2?-D_6Dbwv|w zJZA>$!M5lg(hP>pPM~3nj#9bGR`T4&@^ieuyqnQq;;))A`_4vG!77RKbD$4^IKK62 zY9^gHKMs*tXZAFU<>bRGB<6yn#7sA(-}N{#4?cHH2T_JDCy&k7+>`QhG{cIekeU2Gqik-&kEq4xDpr z2l5IQwPgNv6jl<%MspRu4h;{xq zRnogN=fTBA(nnKAmLDu>o7o@7?Ub~0ya~&lF>`z$$?hL0IT4nU*T_opR|iUg3G-|( zua=_02c__djucdp-zzkfqJIjc_{v53czTSKuW^-%s$8k+$b7$A4XHkFB|ipf%Fh6Q zsVSTzwN1v$?-iTmPZwRO|857h5gVWu*dJ=~N1>+hrFOFe)Te7fqx(cO80P>@|1xN4 z>7&uYtMm%#p>aV0ns)yT9cOy=;#hl*;b<{sJ6igkhT(V%w4MAK#urDzWb9IO88r^( z%XgvI;UZXs-NJw#p%|RI2E!|AF{=B1j2pBK6WevfwBAXW6~7h>zAeB~`)OD`)DauJ zv#?#m5xd8Y#NNTx@a7%7Lp82AesdIargeC~Y8)=juf_H4_PCv~77tc@#}lKGyz|%v zVV2zyvr+@`_8X9#bqg8(N0H<4AM!(!QFyNwC>#4q?2aP#KK3y^atuTDCDw!(q7gYX*L$KINk0#9_SlL9QO?X4WmUDrog5`9 zn_5v-CrJD}=KlRA{t>=FVoQj@s18bWTk`F5t*Nb~=H()JbM~T`J@X_+-Hd#Ku0*}8 zk*E({xLaydA45IoYD@k($lZBzFLhy$ZcA=pEU^#r-KoRtCEbMY?KJk6HeKjJ-6F{? zoF%a}vC9X}5?`m3_>55!|97V(v>eNOuYDxJhIq=zP)YpCnenobq+Dg+x^E#VW=cs8 zq+aF80!ir|$^V~DAD%HeJS|Bs){x`?3+{GylA`Y{DeK88Xp>`zkEBN0fSR!ydd{hn z=4VCfwOUDaw3gJ>oI(Ha4zn=3y}h4gYfv+`a=7H~@RGdPVrrz#r63?u3Jr;IUQd?- z?u!L`Y^A8}V%A-xeB7HLMNg`4<;o8a=B-#pwh}bTWgdj7 z2AWLd?XI2P(8+rO-5oQbH|reqcg}*r?E$5QYC{GWEj(s|9d~8-`T{kFlZWC2YIh7rVz2 zZ+ANk@6r`G>~aJr+B@S^c5~kKXn{+o5^+7Ax6v^e4^jrxx3CDWE=)k!jbOyM6+%^! zhNQbMkao5bnVE}`6FeIEYoDP|PaPk&?nB9~VfYv_2cH7<@wsgpJ|8Vcd7%wHwe&#g zm=Y9ietW}I8*=Zf~%=!xY+YA&MKbc z)b1Lbu%q8+)?FO%D8@cTJ9t=}gWKz+*!H9YHoSDi>YX>RbWt{((^D{2^DO$cD1br6 zIjF5VCMD+74~C}7JNp{q?!zVSY!`{^%$iwCJ|WhbK7j=4MVg4R=Vehk2a0l)qr`sT zzV)YoJD-~<+HMhryS2o&I4!Y@*%uEaQ$I3GlrHq^*bu+aw-?pRi4s4~OX44}-vqFq z7W&YObCKTj;}Y}Mg1VCD%)!-==-$^Q#w3?q;H>}8#T&BEsrfR;b0>{v;W+8l(az)C2jwD&Y8xN9LAc9-X!nn2TIUmz4>t_ zJzgX6!NgR8Scl#|691Uk=i|$gIRBO;(fgPD$W4+@=u2{!ADl7yH5Rx^a)v8CL2o4a z9&^T;IMbKDO_E-!F(cYbQhI4fN+~gxhvXL4u;0Dq_ut9hnC8e_B+k8A6D1{dj@pC0xc1q47SIMU+OC$b!u;rwTja+)mDCLSD!(2)m)b9@rEc7E`e2Kh{lI&$zt2O> za6Z&D?d9+50{Od*+}@poP@6gk>I2iEv6eGu8MAJ8(U;z2AhehLgT?{F(NyCwbguM( z?(!-040%Go`&bxED1zZZAGFEpi*_y+^y*ol(_J5QZ8#C;M?az0+GJR~xr_me?Jz`h zB8IQ7fz8ul_;<_`*v@*0DGJ{6I$DnT2^-+BZwXd;wx@UKF1B&b-F-+6dt+K*fBS44 z_K9cS^aGrVQHTGLZ@6^Q6xUTHxMO+%53Uv9$&^)irN`XOHy07z{tc8lmynPfjO3oV zNYl$eM(7%3Pt8Q`gLvd;X`>)p4Mle>@PRiNiszli2ku)%RzY|l`5L+NKOxhsH&SMe zMMBRxP_S2q&m4?bRm<@t_#Pg-yn)-_C*ittdt741r+-)|PS1VA?3lqgQXPl`e~jQ| zk_HdkY`A#^Ve8v%SntNaTl-5`@~|o9-D`pEcDFD)USj6>^=^l3RoN=e3eN%szx zgq3eZNiHom#fQ8z=fg$ho;`9UHgAk5#|b$G6H&h4dwr`e^__{LV84p_StBv-MH1s^ zOMO-s;wCCl%(=%~cD=~Wr!udLbLFES#29ngW9AUoD3JJ}#6vPHMA?aRqqCMoSF^5a ziE}-nhs6%-5iP@HaFn5}`?-i&p1$XmrLHjdcysB*YIoFJhtlk{Yt3-q5#g zNxzUbTD>`s)(7{XZNMxTw=PD9m~QA45RWc}mtlVVD0)46hJL1n7_hx11|JB-u*0{2 zqBj^5@f!dAvBQ*I*D-VXeawH90tf0@R=HinhF&|dy=zm}UL)-F9}Vx6l{h@48IC{O zjZ=>O;Xk@3F8#}ybJ7FcUULY6mz&^m{Yc(u*2bHVCWvr7h?w2wQ0DhXd{zMx{S`^SR=lyn;(qTi=lvE;@BJ?}zUqh>4GzPyfiDd92l3yYw=o)3a3A?!?s1vK`sj+{ zAL5Utd|!{aE{a;#U}PInu4b>xF%)G9_qW9=Y9XhKV%m9$*-2a^t(;k#N|;(L>C7WHwl-hdGrgpUX~bY)OcP;J8aalY5|QD?{D`j-HJZ;EyqtA@nLUo%w)-HjR18tWx(Anz^DV{SwUF`5gUc?ZxZbUs}&?&wO^8$Ze6@8@c0 zNbaN_k~>q4w-bnQ22Yfn6PqO0)I;*_a?aFlB1I1dNy+eP`ABZ((<0t5;r{q#LAq9`81I+5w1ZNYq$hvO$t^r${n?XS z=t8`u6ZK^E^vDi*jRwKr&~QXQXg&3X_F{7B>+_(aqFCs_8-) zx-LK)t0^$*L7%MM26RZ!MyJ=&=xQ+m-CK}fun0oG5&vO;`ce!EIR@*To;1LEV9c3J z{8x~RDQ=H3bKQ2#fB6^=$MUf%y(2aZ_r_Tf7WOut3-1wsamfEUj$69Jw?-dl z6RMc^J|EXc?8eQHg}C?57mw)uduI3>ubzFw+xuG((U!R9ZL}o_@pu*And8a~yku8e&)C4{V{H)Oq9`EceiXy}}5y zM_tC$K?Sf?Yl%hQx?!~6Omv&R1ZRHNU!T$F(o)Mau0Y(<>%FZ)vERf%cu$*gJiE6!?>^<&7*`APIV&YmMHMakJP zZX4g<#Me~$izU84f3D2xFaeD^5|jItem4p z(J^+kc0&G!yWVs#i=>R8@#=d*C;{$?R*Dq<*{12gm- zB!iyZ^r~=XJdBbw&ZViJU-ItqJV~u1-ZMr|GR*@eyQ!DtxR^_>Eo*K&cRWpEEG8k$ zU^yt+O{nF}XC0=R$ooSEQZ)af6lc{)Y1vUJi&RPZgH)+tUVD{BrhMB^A5cTygKg$3 zzfSO0$?5g-d+J#E9e+`NSJ}z$3(oR;gr@wSZY96#S%1IE<snpbRZVob5Bj zi9VDe=MkIs2nzZMqut^WQE&pGXC@-Vowp8-zoABL9PVv>ih$oMab@RioNro&(^1E8 zBJMhlv_Fac_lw}^6U3W;4%kUdXw$nhSj#-LWt&2=@O3a|8QWrV;%SUGGsP%+AIFSX z2#YRf(dPDVXfiLPVh*)}t!L3&UBf*oh}?In#L$}%GoHBpgBs3qKFk@7tS z7iqw~k-oZ%<`Vsdd^+Dhkw0b;%S@H17Ub3U(HC(jKq4dBNW@CsT&G?+Oj$4C6Upn@ zh(tCEk;v=pk&`MUdW{M9Kuc=f*poAisJn3$2Vsy#1Ut8O%Ie?zF^Mgq_D0+iYHi0>B#X?);UPZ+mbUM*o9oeaQW7~zkI)wC_lN!{<89rTFq*y-9auP z&OmA-vZeMv2lo7a4})XlAsKb~`>evgm*y{iGWiwmIc(+L_!m@|E}2Ac0I zq4n5<_bP{?X>Mca8n!_5dj`goL7fK z_(iNNYlC(BwXuabiraK0Je+UB>-9$*>SK>%$3k%Ob_LFC{)2M^cr$&_FI@AvgPT9j z;+`7t;OrO#{g{uZL(}kLmJdR7C*cjY;q7NbgbiGb@HXuc&Ku6*!=@wbc_6|XRwJ}g z8s6+*g^=ZZ&E`#ba`FZqCa=QXbLS8+Cly!P^uf7TM{#=T2Aml3FAm!g`+4~VdpAzS zZg;`<7(7D z&tpy)y$4n+dAqAvA~opsp{71s-%Jz@meX%UyuXY%|5kcUZxhq>W)J+xx{aicb6JqY z^jj>^|LRNB)oA+v0?5@3lBhiz64iW*L?lj^@PU33)_S6ZX%vKGbHi4r^2h&9O1iLa5Ig*h|32TM#&06BZ|^$~&0<)jAS zU8uym*-30Z@!EIp601lT?su_d6zNK)izjpY@+GUcreuxKm&|XhvooR0j2V##*2l-y|Es9bI>g(~`l zayv*#n4x@pQY)WAE97%AF-;4ld=1E#@A_`?v!kKZI8vSYGeLfh@snStCdw~gWBJuF zo7qQmwRqd^=<3!$!?_rv)Pn8J0hoF>2D2g?V1XhH4uvPNihlU@ zJKkcOS{>X9&%vXtJG>h-!NL0LIC_OyEe)37)IuHj{cex*snk>7X^iV5`rzh4ZQM~c zM_`-Icr>Cf9v2_Qld@iTw(KUJALxx26FT6<;RL*xZh+^-EAiak7|$+D!P9k{5bQq~ zkBW{W&;q!9uLjqXn0;251OKcnoC=tO<7?*N&{AV~U2h8yW*WM6QDV#U&sZ1G11sOo z!xGg?%*$Sd8DCamQjWrur+~mEWei)b!2YK zsW_)bd&Ag}V1N4{MI7?P-0kuK)l2!FavZz7Hx^$Fz zn$?ocTF9=blN>cQ$sU#{S>(YpgSJT4Wa>KWdPv?hOL>3QSPJhCmk-|5X*$z?zL#0j z57XsyVUSenSjt!Oq17||1YQ?mTzF8TBRc&Tx>lV88> z<+t~BsmmeW(#1pm@_w?~>8?=!=LFP`Zi9w)do(a#NaR>53MuE1}=R1TBXp!r(><4A;goAA-5lJ|ob+_Zpa(KSSq^>F9ce+`8gD zdbD>(?{2Q>H}nVnL;L6-3I{eV#lM46FrjZROq(?ovu_=Q{fK|CtVja-9`Tkpg5 z@*M2`;D)_xmcvW23C#mB;J$n((p6iJ7H}Y`la3fsx3B!$0Pu$9z zgu9y^aev7&1S(JBLC$GBJhlrD6@_?s?g1YDw*e1w2jaot+XytBi+f8$aXWDU0v5i+ zRrujT(iEH>{4abRbEyC7h=aD@uy5^TxEGAXjs>T%@xntm*#}_RI{uljjK=JW{g}4y z5hnB-gMS;uK>p6f(0O67!aDSe&67X1!Sc1^;s494%C=@Eq&jyQ!;7LU>Ol|d|7y!v zV*1aL7|w|?opmI}Dnw$8M@e)jG5%)6KZe;$WR5enBpwnq&PAdOblK~;hwXNhh{1d$ z-I1t&sR8S$qMuzv*)Cs{0h>hmSXY$mH6^yTjl|A8Pc6$>a(~<<{irds)RE|n{?xxD z^A;w(33K$Ix&}XnW9>!FDeV_J}2o*yb&?e*-hl#X6|>ZY9whjIfP%=CG{_J z`qbH@E{J439z`AWbY}I{N=}uF&nZ3Ff;Ey)faB-zKwm$%B6tZ*0dcQul| zj$WZPnUeMXsbo>tmNoV{Yc-eowsn%PPEMhKneG?p5t^efC8L?QvaVP@y}Tozf4fU% zqt#MX^h3U#tCi|9H~GH8P`)oPmG5h))qGYa-!osz_nfctV{WDVkUocG-y=^K)ZY(n*5`OCiQV>`uZ+(!snq` zy&9T#DukZN1+=gp1^t^DFc`lHt-dctYh_y)buNSPPg8VMdBC(=Uvz0Y3TB1}U|xO_ zy$U?g@8vNJY}OFg`>J7M`W$0h%)&(1D$Ll(JI<9RaJb@()q|3;!Crx_C5y2uV-eiX zzr$W5df7G&fOmcs4sPR(y@8cD-gpXp-qzsMgdsSi(1icSAY5QJ+U2o(a5dHr*WZ>P zKm<4G>AltB6>k0a8@C3S^UnGq+$5G8keG)XH=5&G$qQWBxCIyI7Q_D~cex1Wr0nAT z%<)03YrX*E>YHKI%v5Ry)G$~v z3jM3z!eaSm^g^B#&t<+qwNf&+`%uF{yv}S6ccDQNyKocvGU~B%o%sGdD$xTfnRjF> z(NiZA<2RHj;)juam=iROepZuk`gg)5vej4me5kE7@|1{ctUbPeBNRar`HlVXEHy17 z$V2SXBJW4;@4paw#dk{V)9d8m7l`6{7xLel5|ht|_ZW%kTO-lo{QhRsC90VReRS!bJRlHZi+dl7(l4HPCNt!x~{&VV37WqiV5zdbPu9R$Qnsa;? zNbW;t=7qhKyoy@Mo5A{hCI8&n#_5vn z!CJlM!yE`t$-m=7-KM(~o^g>6gDa(^#eVtNdB1#GK3qOu8!un3@-D0$J?Bp=<=dWz z^6d;Cj_c*yCJp)aB2vD6GLh;&2J*cPz336lcaIaP^|zHe{b2cHXT>}Sa(Rb&v$7Sl zkS0Yy?V~+3?Mhvxs}5Oc|c zerw+K+U5juW3zMzMFg<9EuFIRC`^X6N&YBC$wo<^_CCT*0X>t8wO)JN!-0;=JioTzK&j7c1`KQov7Ks@;Xl z>Ls`obDH-dN8{p#MRlyNh9y^0R$Mn!6;5Ez=H6-15 zo+QT-8`xwfsyD<*9@O$?fG+b+Cer&#pFj>ZBOZaw!*r0SSAD48I?VpYK6a0JM=NV3 z>{_mbU3Qf4{*xuVg4*T7^z0PU!}Il_MAVLzNIU8(yRjF|>Ox;Sd&!>~dccWW^qMEJ zb)01nyp&jD^6f9p*=Od6qA6=4i2Z7mjYL1Azg>HiM0GWj$j$vFa^_@a648@)jF?SN z)?&zE-d{J6$hQ3TFMEml9U##K^%65q$=CDmFphmTl(lw&``daGiG9YM)RDVsn3X6R zou+@uL84FG;XTbFYG1ZU^#8Ol?)=`noJEm!OO(yYk-VbU!OTYD?%0ZIiM_<@2TA-h zYN`($ly{4>@oxYDGuKOWLd+VrC>B9^$>My^s?(Wn`jwbUP912w<`S>`8ZOyCze)~kIA;<0gGu$0vtqpDHXI{)%@;_1 zr%-v{ot{|hIa27u%p=2lQv8hm*X_(W+7>9~M~s=@o-UPGELHn>m$F%rRMk33mGX^L z{p8)Q@rUKBUw`@5_N7#h;a!Dh#!}PRLwGesK}VaNFtv_D*Kfbjqjx3xy!638XU<|!rWuBp zUB#G@Q!uH^7|ibLfF&39V~xXiY}`k`tl1UpG`feKb&lB8{yKZ#Nw^jFgZrYs@Mv9) zy@j0ru8e^9?A`=)Hmb0HMjAX7pWq%h03CEhbJ zZ}(Z)=`Y13?w;d9H8C>yHilI$!JvcB@z3QD^qsj5J#Y7e`BF`EZF2^luC$SaQu0}Y zwIpr}{W_JzBd&5jTdFD1Llc=f$@g={dgf04k0T>@wu@M0+pqKqlu9@;|IlBW5^Coq zp*wh+q@R|A>w8N0H|8MSHI>K_^%D8wDDzq9ABgA7xNfJ!EF{)(^q$0CAH~c{SBWj< z>^G5hka}Gde#A*vmx{uaefF_6IX~8e{aA^*#2rtEyPo!Ci8|*e(U$B}L((NGBwwP; z$(s{bh>RK{Q8w(UM)W2%&}XLEbBVFPE-~Bq^*SAvSd$=${X)*5Yq`Y6FJ=z)JW*WY zy!nYUb8CLSEHq)Zi;F}Xv#0Onp83DIch5nIWq(qxrRK90H84rXMHOKx@rh>4)-;gB zAqOR?leQ$y;9l2^T9r=JKpSt8lv-*qdv}mb>LarMLJME;;rWiYg_B`}|5D;h_BM3ZY%(KOv1&4xCIuE!uW_oi=e z+ikRXLcYOiAzC@TM(cH6XuEFM}sdgJsC}`b0&IhZ-u@Z_tC%o6bzYu9yUXa zVcY%~wJgW5Wv;sRrZequT;n-GCjjby`@(#lmYPh4f_N?D8C6b(QlrFKwNud(?c%DT3$2`A_?h-n|i5b#5^7e5pPI~`H%KD+UWjs~A8!OTCqAr+XZ8{^^s6XrswH-5s>D`1u>Y_J=@ql) z)I_n%Mig#~MPd7fI3u55!`ko2d3FVRQJ1OAeLpDCTR8)^XHE9brH7XLr9&z?cGjdl zxjtvk!u=+a@8Et|>M05h_PMS`qKG7>@`^L*Uj|th5Kk239hx*9-G38QV79fT5 z{G{lp6K|L}Nb!a`DLEX?+FQV^l|k~^gtwWWt(PymedS96@1wUgkjg7BrE20W`D)}P z-x`)l^$2tMerS;V2r!nPYp5w3oGHKDHRQL0BRK`?C)|Rdc6mJ1sdLiUK)v+83(?@0 zJ~Yp7Lc;~jz8&xtT3^nhk&6u)Ko!QP>*WToz;F@BUc-$`|Wyy~29x{Wny_VPpDaY*rOxYjr4G z7wKVFKU?e`GzRYVt>N*g4xSxbW1sa0c=ar2Zu}g0-`ET9>YwmVS`6=b-{E!kH1_S( z#@m`IC-e=U-xw zPc;5f)T3{|Yv}Ph8)hfGV9LAJCY@r@KJEb8ZT}Z-O3#x|Godel{b$v9iJimG|F=(> z$;W;*hkMgz?q~L_v(Mby&RG(>ARjl7J5{-tgza#U(Dk_z+RA|0(RU;?!9~K(vFCA@ ziFnB8{yk5PC9%RjKP1M^NMg(uNX#5FQS>0D-;=ZDExxZeP{&fTQWQRysj0T*tl3`_ z$GMmNaALpHqW-yt8cjYPyHfv>DKWY})JJoMTy#-lhOi(0btZ-xDNzsoC2Cu!ME~P1 zF`mhy*hX%nV3x#oaAD6PC+C(-9=wA1zb5^=nISVYjOi?@6vF|`&%se%lk-v4Qn{VQwn}&O5q=7Nr&3V zhe_k5xSfHNxQEKe{igCMkouJ+AyP5*uvEk^r;cT;RBBpF82Od-e;JA5j`oWA#*O?kz0-7z*`iW>BwO290qap>bgd8gz_DgN%dFd^QUW zkDP&4rwC|m(1g~@zGyV10kr2kq4Cq3XsS0FI^C4e`EUu%b{&Ln@3Ux5U$&mLivGR5 zXnA-9TIKVu?CzszH$EF3a1)*Uw9s`&F?x);1`B2l4(|61qyGJlDf@%4Ft!O+SKDFJ zv0!Y=?TxL$0++%CSZkJom0#Lng^4j%{L=v|JMF@%C0npM#t%-rKEc`RD>i7SVq>ix zw)Xf5S3?)Rjxe*b%$aF2G%nTF@FF?7n&kyY>IVt(y^c9O{p)--5C6 zj3=CLXkk^qR#>{MITpGoF?-TtO#M0-wkxM#bmcmXNTc?2=~N7G+lhW{cA(d<7U53D2Z6WEFxF#bUyYH)^M?e{@x;C{ok9%u$&v@%B$8t}a!Sd)a?20z_d%T=Sl#D8}-0 zA$UFgd`BgIL^*G?Ys))VKS{KqCewVmB(L$^Chp_b0gv z7MDxj=2FSq<1cwVwIuh)Wy$NVBYFOuRl97Ge3g#8zn~=r`^%+}+PuO#4=GwOQ9iWR zk>Z2ZQhHTW%3kJ6`6TiR7Inm0>ZPJ9Yp>-XsT^l2RR_7}T~?#d`G-_HXG(QRoqV_4 zFW-xc<>$F3QhVT`)LYXl6gL~{@ytImS_$>PanNXB0Sy;pG#L4i7>hF+YER+~%g)Te z-HV1vr=Yc9BpNO63GE;Y?tGqTlAngA(KB}(`VZH`h_;;H`nJTJ{uXeos>j-rLTp&J6YF+F zV0GL7;Mn>R7Df4BVF4duuEaJ*Vv&^=77v(W{P#Q zsZriU&(T;bxU?Dyms|7Ul0kn?a3);l4#0*xEwFx)2b^2@VfEiitS~wXhaELoIII8!VIC5G zQ7Pdw3?(c_SHgCBN|-C}PnPly*P<>G{@Pw5ni1!?!P6JC@#qKGP-El$w!QVb2J4L)4{hhV8%S1e1kz|v%Q*uNi*xfdca z^RGJWJX2xkmIk|@J23OWAIutXAG3Rp$DGUhnD_b-7EV8eMTI(8Qn(LGJD$bTw@2aF zDF}}1Si9!};do{N9QSvHp z7jNM`mC!PO37a=h!bg}ehk;ni9XpBCkCw_-Sf?S1-Q)p1s;PV7;}U=V zjs4ETinq1=B(^{6&e?}}&OuQ+6|gt06s0M#$BnGN#`ll5D;vr;4u` zY0lng%AHVK-al9%1qNH#^NgjSmVNJJq7=PlzdOK8>D}YyV`a6JU2VeK%>GiwyD^_u zUzhS72J$(2pcn$a*bn%kOLx}DSM zRHuui`(5|a{qFbte1CtONA04*dA(n+*Yoz0etr>RY);>7te%*Bm@6iE+r{+582V?I zin(8d^e5&%VB7mL(9b{yUC@Zlrv)-pJ4A*zzb2z6ACYlgzlr;Wzr`!7M&^EZR+e<_ zl=nY)CCj6hivK)%*3X;ClBB8P`;Mc$eRQY1on9-xYd#m>^bch5=&Q2$cLQ1SA3bj2 zTJh@(l=mkGQv=gamY>=!%dtynVV42|TEaR5Ul`)yGiDSqL8R3;J z_Om~ep;x<8rZl{G$x`(MT*FZ$NS>JuoJmw=O$Ddyut}M$A9c zAF5GX5Z}u?uFQFe+vJYeTW1k#@CY%XMu=g)a!kJ_#5{~dY;6(wJLaF8Qx}#UO|F4G z=vVGxpIRgFg)uZ1me33*U%luE?~Ck*Mm-&xXCBZzU5_NiStNa2G6iAtzn>r783A6e#M6TKnx$p32-x#xbo z;7#YsJa<%8+oF2>Bvkb=W4g=<)eD$4=AD4rU1!+$Y|t=_{5myfFRwpBv(ZDeBaWL77=>Lx>Eq3aY`a86(5 zg-(_^{suDJWueTTuv}&r708?$o-%hxm&|n> zh1h@C=W?`&Q9iB)=5)OnQxi(vf0IP4f)i5wSdo zOas=zI%^axT!FHuGf_=_Xx*x6)NZDirsgn;U#CvebrpTB%w!L-gzj<((#CfoeOMFH z>0?dLrSJSsIMN?9BE!HE86T!22ukQ#=9!OqlQK1P1sB=)!x6x^DFg75jF{w4nfb2Uf%Ah$XPIYlMN z`GuO5x1Eu9v67`(Aq3|2lD zgAaK7WYr@vxOGvK%nLId$a{Midd09YR*YP?iBXun^n2~R7>`RAleH=_?a7w@1KMOj z)HAWF%@dnYH?iB!TwMEu;uIYs zPgyKu&-%(}delei7K?+mR)%e!EVhsT7Ms_e$)E~78Cabymao4j76tD~|MXZfXC8$4 z`)kGQDQ{8iq3?U!I(h?$m4EIDty4I4R9nd<(*rQx4GA;p0SL|Gt*w!WZCira!MhM+ zXoDyHA0axq5>LKl?~76L{yFt2x3ZyHG!^Q5Zp1$BBXJu)_TxWaqYfnY7%>*|=8L(r z4L(Lr;RZFGMNn6hYrB1lbEKAD+Dd587a^G(cJe2jdxNRpY~KZKU;?!I+zJ2lN7Cn+ z#6WLDtFVUl?m=iD^5YN%k}j~%74x}qocQA;`g|UxAm!vd=uGw_eFSr3XYtmYQ5T9% zSfO&DGqcPs(Dca}y!>qWGeXxPNix;3n`doh8<&F$9W=Gdho7rHAr_KC2)z^^iQ;i%CcjT=nR${*u z1!s3~USvNTeu{pcNz{#%A;0$+``kS8>X|5bjeYU-MdTf;KrXYEa{i*0`dI*SPaQ_y zx8BsNFvq=GjiOoP@OJJ($#x5rPPl~9VtRV%aIk2<2t zrT~>McA-k#P>mqeCRH`d@|T zccS>09==8A#o)nS^6uo`-J(Ugag!*^D1(l;BSw+*^^FM-(;=_K{K_vf;3{=4>Bq!o zNPrA|QXme_E;4$2j<|T&i`$dA^v$@4`|J}kW%dP`awkAMTwaPt)noA-^o_i+^+%a{ zf3Zy4x|FXlnKn37rWU^;(1)Ny;0u`%Gg)Q~ww7s$Ch~@ApuA4~*W_!3GU302GL9bjF)J3ysBh}TVa`Pv zc43ta9rsFXo=g(!fSqFHF`IWKD#aqhPlxk&DsHAU=FJ%jvPZ8-VtP^g2<>C5Yas)#!2t?QwBhZy83 zS8C6qkhppQcR%*9tV{I4aYp=i4U+%i-l=~D+NFG1Eu-g%7--umXs6K2phx{yQ8ZGl zh&e_PkKOkO$u}P(If=b4o;*PV{Q{{cq1ze8y5Sxcmrm_Jdqs)|D&IcHyu1C(|8Yap zb}Q8V!aP{c%>{<^gWm&bPx{H@4E8dL6T3hFPy&Jm)c|*=1_w`!j@_CW__Z8&r=YBY>0{M?_Fk_f?XKaMx zK;kNgK@KkrWn-B&HqHhW+nL{evN!fNi(dQ#YO0qow*~zFR*Jso8TyI*M8D{?D8|ee zg}+)9^zJC?;>F;*<)S3#XBabF`h6QOCgoSfoL+zdE1t-}j%{L{caDCd0b;-ST^Z?r zT1K~>7v};Gap?^d*9|vh{KXJ)D>smdaz|c^+9s3TH;eo36*6VkLz(i?N|`c@|D5`R zxF_k$6#LOK<)K16w!6#g7GKEgyz~D0mb>!0*8=elQNc^-(h3?6{0wvWhe1NEs2jLhP*qW$5b%#74u6nFFa}#XDh^vp0xE zn5pz9uVGf3Bc?B|i0P;!VseE1iesV}*Z7L@{XQ`cIEd6o;Yg`rk4fTA^#*bL>8y_d z)MJe+A&#k_7I_U~!?cKv>_BWZ@4M3H7IVT2PkLSOydExfB71QmBQl@on*C#mat!xjl1)N|73tr~ou|9=Ou65=g$x!<)N zL2^?cl4D$;eICl6sSjFD`h~VdF;~K!+L2w{N3)R98I06-*C2KARHQEBt=0hQmOJB+ zvXU7MW9S>GX@Ty@5~SZEe!2HNp6};wHzW|{cc~#xG>eU0M`+C3flpU>ZNAPP}pn$E!mtM9*rR=zZWVdSPFQ-uLtj z?bt1Po~C$pr5b(Dl<0f!B>EPIp)Y0*UIo?TRe6c%dGP=Jrkb_*w&)KSCi<(QM8EU{ zQQZDl46-+fVQ_@>J2qELI+MhF4sY#Xmk9@Xw;}KwnXtJ=Cd4n4 ziSG=OiDen`+B;X|wUEE$H6JUPnA0W`*N>8kW*_m_+>;5Lh?R^EWj4h>GQRwYxUTaS z7yEx?4Ey`2osVRAT%Xv@wUZ(EL98Pj#p?ZE#d5;$V)5E0G5^|7%q9;NQ#a~YsYNr6 z@ZdcCi}X`F@!9e@d6v1-&*chl!Cd1_;Yg%LIw0k3_Pa~b)Q)ftTyvhaF%fZ#&mgAD z2{Fgn=enyAOB^lE))KMZrik5q8!`D?5u>Jd-SUUcD}2?s|lC!Dl0Z0o1ua&(EfsDBWbXImxlgc|eqApnGyV|c_rs;%9X?0K{v}5LF_-qsFU4ri&tf#TRE$hg z#W1p73>}P-{vLA-QZ^#h;}nuNW%0fakm#DuTRh|s&XE^?ra+8!9%44|hR+ul5qH)T z@#FmI4WK8$pbW9(8e-4JATBJLyI>>Yi!LJm@-5bu8gX?S5kE|e_~rcfZq!ZxMh?)A zug~b4OIU#T$qtCGa)8S7HWHn75w~1HT?u#1HtI#Egi*V8l9{*MJI^o&WgO?r4EC{H zSLl|SBkh;nynk$q3_~Ae48Dc*y~HD@*dz5e`}gC=NGVT8YC{>)wylpiF~>Q#n4`{7WWJx?dFVn|d!FIYt=SJ|Y5d|M{_p4ro!huaF zJZFg_?#{*eH;7|W>-jbBQ_fAG<})3o4#Z}uy(teb!3)nKRL1N@wZaC~KlGq_UnQzn ztw43B9cmBOqy8sPG@h`*%Vr&#UOA%qxjkCGS%p?>>Q`bXp{6@3qZK;;DnnuE7f zpvNdjCj&lMEdyJ7#MXmlKAs^?+gFRzomDc%Y@v+VHBQDnUoK;)GaEy1(wMo^#A#2O zjQ({Fz3~slk({FAkj*mc-7pzZ)F#6(n~9xTB|~ni#5(*Z8Tiac1`tE)KiEl3E6<5> z$4Am{yZ!%RMn*>DGye)_4?ih}UWdhSs9FpaN>MiT6J^3~QNH^rF{iJXwY3%LGa{f1 z)zIVO2JM$Y(7fTptcKf&|A^l16>W%FFcC4UvJp!iUtAGy78FoJmQPI!^SR>2v-Y0S zgF1v-mCeTx_w{c2QR&qgaTjr}tgq$lkxR+7zod@r^ku|ek3*dO7V0?3`&sjQ6j9qU z{Vr>L9Wj%sNOo;O@}7;%TR4NH@<=3g_(OZ@Jd(X{F&l+mfHTBBHW5Q~@IYoDGb)|K znVILtJCNkI|G3NSbv>lcu0=}LMb4qj!JJ2*&MNMCsjHbI%f9#LMdS=BL+&*4+G$I8 zOGJ$Hc_Q71IH$J{G6zQ@GkQ0k`g`N)t4!VkI)Z10E_fEc8rj57p5L>h-#iYv zqa2XCYZY=&(_`nMK+esP$hpU!_nbLK)Fl-BwF8AtI`R+9ZFivW&W{?;Ao2$qW0dUV zyxC)m;@Z_H*+QN4SIk`L(L==(;wa>6s`?yIeIN?eOFd9cjb3${18P5OL4Ej1G(I_x zrcGAVtUN}Gw=r72i$P23NVHP(({`r}9V+&%w-V6P-ojc7AkN~0o_~$dbEkxRAM*f- zb@#^aLN7G|Jt^D=XVO3W!+CUU@<&I%W9V>l;O#HoWeBZD$A5>>dFc$gDsQ7_pa**U z&qMEX=H~{pW(%9>vkewKEp=$SZ;Em&?=#rHFa36{730dmyh-#_%rn220mJsoz}FTN zb2%v16Ci`5=^hPn2Gt ziE?tTD93IQ<jMKg0xNiLxp z3Bk-WS;o_D5_^6ynDRF*m&y2_Nu2R{UPXRbN5eLJ#R{?tk2 z`*zhLw$A1MYAp6&?u9m-OEXs>*2fKTrdEh!?rePEP9)5re)>}zW?&wprjosF9(&yP z)U36Kkt?u9(pht8M@Gx?!M!_-5r zzAb%s#6F8SV>VJl_VGGuTFOuqy8y*6*R$qAQPRlQp8Y89EkW^%Q{)spQ9760xoS5m z9><}wa3rd?I-~$rl(6Z?~S`Im)V6P)o;i!W|9rdZWjAdyT#r=PV9qS#eQp(*dL7)`}_W4U&7a5$S)od%cCd6!fAw< z6^|5?RDbFB;siNL>IN=Hh{2gp#9-t-zEc_O2+x zsCSLiiN0PFvR55IR@GxC|#6pq*tyUCm+W z#>XLD#kq0nF+7`Oh3vhY;VO-IH~c=bF0=2M88au^mRja5NbaC#{Jm??c3q+lC7b_{we569gzA(3X;1ONH(5IJk=Y?;jFzo9ncNB%iWD!f{PLD9a{yuE$=tH3BuWT2S+xyupER)F}E; zGsG3OcD&Qo5sJolZP0XpI=l6-d-+8hBhPR`gKaydqtHkc( zZ)KN4WLE?~`t@g5}ePNF6vLBskB3#EQ~8FiWLZ`aS# z7qkLTez8J~3H^AlJ0oswDB}L5j@mj1u_Ii`_3cC~wS#dZ4kKRZZ3yB#yWt_!w&Weo z@1^c51c{HMpn0AA_!?@%KK6rVEVq4s~z z4&=m%{t>A>3qEw5kau-|RriRZo;G$V71^Yq4-Y zbt_X*^4$THzGH_nExmZdc&kKLhMH#TQ)*VAwq+M;FWpD&`7GY)I)nQBHZ(TuMN|GF zwAgf^g)?bOP99q4>_r=Q^7fm3=sf8_9?u&+=KPo(f^Nqh%z9^y{m%R`Qysd$U=AQ> zl5R&mbb)m`eGNK(h(yN+Iq3LGkDqTr$0s+?VaPg82uFKH3pz$7pyLW}q2EYv zEIh`@fZ5(+`N=sMc>Wu)ioGL)sB5?8tRf<1-~tyJ zaDAinPi3x!E=Wv@seN;#7UyT`!F?3;Mhz52D)ZN>(?oweIhHTV$#0U+JV+gZ3Gv#= zBScYjSro3dqCfc!@-fV9Gv@9-LPHLhcc4m6;@LoQ#rKF^{Id*6cm1hBCcizD-Zlks z$z>_@veMskh<-fI7YR>@y&TR%f>9*m^|%LqPCbk9GIDwjc+z~FIVkRk+reH~=7G3% z)KLF46S31dhpyvnN)2-SBxJvFh-YHF3UxI>NLT)8kB*`?0NNs8e;Ip%IPaA*3MIk7Wck?}6|Uaaxt{^Ug( z&5^i~UpI9p5|;!*^DXtD+BMMLBX%>^0@|iFXx*KVw1IQ2Gw0jAtiAapKi!H$~iM`HE(rQAZONk$?o#qVc#(t?liM=gKDwt)v-JhA7(Wtszf!bmI zsQXEY`geJ=Bus(&^b|Dw@DPp9v(fa?ZD!kELJN7Emb_DFouxo)?mV=mvOcCTbL>CX zQ}=On2hBvcK@qzA9MG9@pY^f`UGH8*mu@M#*ju|+F`F!$y{$EvHMJ2PMoRow z!W^@JNY)(rcsqZz{j>{h-&muqbT`^}>6q){h4$Bb&=!6SZQ0xh_mQ{QOP=1tTl9C( z1JPhZzY#>aop+!YjuNA*O4DuW=gF;GWP`a^Ldyf+9EABGb;$0bhZ>rdwnJhyB8hF>iQ-)l;D?{G? zR&3%OW$?eL+#hd<)i*n3pz|rQG>sLD7iXkDITN#@(PC=wtr$;TB}RW3i{U0yQGRfd z+ML6pSpTc&pPVE5{gQ|iJ!JhEh+-0b`bkSg(PJ)(Ut2{#Ctvi^=i}8{&hFMJD6Bk! z!dui&IMBaxhws@-%w&CI9{Fi|Y9Y5G(U3kH6?uR-dt(0nNSM!>xelt2&O#Ly1r@W8)PccJe;+~r?KP+!ed%{4#+tsLnMeuDQRkkupEp68vyffJ ztS&HjGNl&zMs3LTK8RHPO88w^11%~;S5pq$5|9V;Xao?k2w!I6fH|f zkpVpo5LPr2{i`QOgxj?S^1DI>$t z<>Z7e>q)FR;wgMCcigT-$4A`b$T79|UPF7g3)&w?qutN~ZHilHbzOs&A}_Q$&qM2h zgJ@lCg_dUpX!+h7t#7uVeY^%;%#rE+nVkEHjiO)gEQ-J15(CeLVo*zu`y|eEl}=)O z`=o0d&&AB&O3Z%SDdr=n33>jn^iQMDw(JcVkp8AvZuA$+=n5HV{jm(ZxR4r> zwPN)oQ3kCs73-;!W$@_fA^$MI2=${lgzo^BP1v>9@z|*~7n6B{TN}c;~)!1)h%$AP#4 zY;ve8Fy#(79ceRCnD5X-zTAyBDKGO@Ghat;AC^mYA#ig+~aN{C*=`xwuK^R(thMPdL!qIf;oQC$SbBt{Rd_p z{dWchc6unB;7$JE0O!m}D1Km$qQnps#?X_#m{{lGXcXCYP=_-T#m~s0hcaXA3h~V0 z)C1h`LY?h?G|Xeh3bjT}XO5w{*BPx!9a=+E(E9lb&X!haogKlw?lRh@okY8n3p%C; zG1Ji-os|dC<-#7v{@poZ4mu9-e&wMobQ(mVb2f9xh7_SAUdbNE{x-i2?GKn0s8plv z`XaP(o^1ORX#JTPN{6&)z8i<;1N1&TK85D=-DnQLRoQU-+oDV9r5$w1?q#Q#r94b~BwK6cef%jJQ#ezBO{kJ|A^R0Gbw$DdQH~cLo14ff8h!(^08KOMDNDTg9 zuj@Zt6yMDh{jRH`?>kxa9#G5r4Y|qaB+++CVegI?{oTQ$KT*f$$$fhGr=$0eCUn;< zLc#mDQ8?l@^6D-ko4&~Onl(uIr4rh)3DA^#Bk{>2zE7t@<;#7HJa7EvNr-2y#N}ra z8wo~Ss}bV7$n7n5L`*>oV(rZldxQEi*Y)(Cml5ye{x_c3@tCiHcctJxRT-`&xwwhT;t<0^sHIn&9AyCEd zh5C;6uR!^S7xCg#-aU^AL+w)L9F;NSh?=uzi(6>< z`94}tvhQdD(fUP;cowZ8tI_7Z6YUPfM>=++BZ~DkdLudwozU@V5ZViv zgPX7Bt&^$f7B4*sA=g+Jc=DpM(LG#>Tw1({<@6d(rT_xySzf|;A6^Z_YouZ%ND~b!$ zTHk&|pY3Te`fw#{kNS^p<|^4f7h~g#Vmy)iU-oNaYW7mh)}9gb&r-#_lQ%M7bC>=b zKau|RrDAa=Mg}Z%7E9jy8R#`btlYj7tDj87YIlrS*?%bmAF(e^R*U6~V`9<%hV;Mf zBj#T%6SIv4r93fweVr)H_tF15OcXKHroL+=`sy8`Z=WlAhj>#7 zn?=u?Gv$i?qW9qo(VKo1uQH~huY@)C&;dPL$!ES(LfqOMxqVZS0!~P{tM}2Z!Bx|mmda~bGXMyJtcQg<27Q|pS zvyMJHj(ERX#4jR0{)H#vwZv%LfP`c0ZO?T`$XJJj)q#k=?#lg;m}#yx;#XuM!9AKe zN`6q;SyE@lT$K&PTm~*eqP{CM>!@3Cw?fKL;)+V*j8k}rF7XVq+BMA1%f?gk`e}9S zQw}ZEoDrw_iI~lU6#jaCt-b7{&h#mvk6yh1s4p`|B9ULeo7%O9Wr**NM1uD`sN7k5 zAJgNIN)F?DYDK^3L+UhYV@~JMbI$&jkcqT?Q=!Y^4X=-t&>dnnRHqO93;Ur<^M&qg z7t%&qA$=!x($@6pCG-AP9yx<49mpF`ZDx5E^4`g$29vjz=k3Pxzo{pS=tIsM#8#Hf zLEif*$RiG#PcElmOACtJmZG?ux)fy!^I51>8OU6*YAenu#=MV5AJZ&76u)UhY^4Wf zhFej-o!Nd4H&I3GsOHLH)O##JjnL9Tpyq5ba;w67V7cK>5&U(B7X=@#p(4Q=O{1^DMOw9O4d zs}H~SJZ1*QA4KzR`W$kdSXah)IinIUoloNB@i4s1q89tdJiOFbqw)TIy!1YRraL8Q zsd$L?Gq=$dx*xrZHsTdEk$O*l5q9g~ePSk~Me(DDD29z;w)So6Z@v{h<0W|Yb|Cs1a?tnJNc3$4dMkIJcS;_6IWgkj zBB@7MgRXO)$oi!K&wgKmtfl0AU&J7#dJVPxnb0iYEVgeEvli%Yn|cYVKdISl@JD>s z8pM?nqx>}tagD6QS=57V=|X&Q0Q(8+bL%mv!X2O*uI3KNd`%VWFya8>o4csZ1XQcJ zvzfA%Mst3o7d>I^O!D*OCzR}c8#!x^bw$Fz9gvOVra4 zqx{Yo$rsp*x;gvh6TknKeLB4nY0HQS{Y6}|iM_0gdtncM{U4XPE3(Fx(;G3Izn|&+ z{yKV*LYVimc{kL@sUr>CO`K>e5^{CC@fFQHzb&lusnBdc3+-fb46WRG&ylAqIE=K7 zYIKqr`+g`tYm678Sa)U`)JQbyLXFWtjcH>s z^Bt@?+ZAP@c`36s!|Kr-o{r|N)o32U+?BZLXgbIFuWSeBLDrw$0kj3OuZ?ECy|n`E zt`2CoT!Oay8ng|MMC-pLXpKCM*3-AqQWJ>g-05gGS%s#HF=(0)ftSmfrxZv{TKa7? zl=<^>@`ii{HeQHEMQdxaX~*@9uwCCvlNk5l|n#fy6yc%oftJ zpEz-Tr9J$qyu;&V8s-sk@Ik~^aTy?O6+FgL}G_qObiFq$*wrSsIlFt2PS`H93Z zs775O*pZ98{5Z_1%phsbEAbA6F59==@ zagKuZ1Whne#TiEk5A`E?F7e)P;XaKC#-eAE0U5*@Cv{#HS~jKAMWZp>3! z0ChZfy~GM64WnM|H(R7E(joQk4d~WVU$D>ysS(U?A90^~n*98oBL3O2FFx?*9ZTXb z8Pst$Bv4x&gr{bKcy@gaa!yf$d5gXRrz^-?q9umuN8a3)J1OVRuZ@s*XczK#6Higl z8#L1c1u66`ImdCf%tLAFC6pUHMmc*%IqSVFaTQ9Vub`yLkNRj|ltlTUbOW;^;3rXgatAO_z$$^sWb9{>54u zWW-*?&z~+r^Px*<4X8z1#2U1Fu>O`sklV9F+mB9YEn{77@k6T%^)8o#(0tj7JiHPw zqnKH?XgzZSSEJEjCmK9bQ2*c->hC6?!6q0DTYb=Q_%RwbdZ1ySDH`8ehL=C{89?n! z>mFyc=ft3E*D~rtBk*e8G12?|f#_c$(wykUBy(R&Zl3pI;FCS3Lyi*L*uZi;HC^0y(Ulb+& zqS*SM==V$!{e>GuZ{_dk`>6{(zu!mqY+H2wa)nvI>}`MbplfvnZx9~mj!Yi&#a-^l zbI`fz3X=KvlJa6BQr45(y1ovY8@}u&V$OsiT%kN3HDd0 zd#0vYMV!K%_4VycsDJT*M%f5WJ9}j{Yp;MkZ4vudEU}nM8)~G@$ptcpXnPqFW5|hn zx+3vJAk?oD^Qc-+UXb+{bPEaBmBc?*Lq(6K>fT_5Ob+?{1TS`vhfBfD| zXWnk0kKNyanVgfDPjLpCP-YShb3h{dNMip!`uq63COqW4na=F?7-*IgtL*22q)zH$ zey0wc?F-_ zTT!9*M|sU0lvOQ5*);1BH0)uv=2v{rzen%u1AESZIy5$sW0)3+#(`b@yb?{>p7g^WX5DGf{xz}A|2PZo z&ZK_xEZSxmqxDbja;DtljJ?rxHw!Q6v2WZz6AjrNXzDJ7bd1To0)%hQ_MVD#LVZkn7KeqRm^2FEf*7` zRnjk)yupvmky)rGO6&b%FlMkQLU|L{%xKs2ps;Tw9gx-rs(7i;B&Wi`psqcbL ze`9oNZKzY_i@Q^YhC4I$f1P7ldna~6b8Ih?u4E(WND4IH7$Gs3&-++Y)`v0Fv-uu; zy#netN0MVp=dMLe!hkr$SbwPQUSw|GE%M#`HPeW{`*a{_(=qx7nt1cP2T6uIkQhkr z?=Q}9*L?WmJoh`_*KgR7LnBt&^AH+e?pd|Bpt@{K{DgJa&N)<%U%$TxRMRFxMZQ?= z%DUg;PoHcIGxo^EEhp}A{t6O9XCjfaoO;U~s0Xt@xmrQ}Gv`N3*8Cd^-f)OP;?Uzr z^w$uxxlR1w!EK)P^qHq!K#{e&mH=*Jjy?5Vtp{!sg%8HricU6aqR$FGQ(C^w9 zjT%$lf{ogX+5w)Z^$S7m;{wzLT|s@oBQykGMB_W*Xw*iaG4>1^--|;-r5_rNba**Z z$xLYC1vOs!C5m?i}rW#qwU#a;-B=XPqjs}iay2(%#Da%!#dKUe(OZk z-L^xWw=L@QteB}}joNz$QQP_mb%Ep*@ARSW-#FCOt5JV33=MafFW7hsO>4<5%;&s$ zz>hd7XEXhi=$+ezSKjpgz3~sdM@L0(;TNJe?VRYn^`7YMVZWLAttft4B+74?V||(2 z+Qn=!rN7qnOX49Ts)>iV((6_v{kEa;in<>sbgm`eGLSmerWkZCsAM+xM07darq_<|Q{nq` zFFCS7)==Lnf%?!f<^>($y~h-&$RDU`_d*qu0M*$x)>{wM9;|^@_LckWTVFG8Ve>U; z*U_u$!S~>q<4FEE8rsi0pebVh(CDpD^=^TxD1`NV5Sq!%O8=CYMI!T4ELkV_iinr8 z?p`c{>V}5-vlhfY*k66Spt{ce%lv<}-@+M?y)lCGsf{=N?5wAc>*-rR#Z04mB$QOJ zXS%Yk*FpU)d!q$k6Zw1cxQN76#z>4_1x=7M^=8z5=Bz`?XXM~+wLmv_BXmC3kb0Wl zp-~plj@rdr3J3W!a|S-Hg?a&Z!Of?T=&B{gbPY*I?Vw%kK#nB~N%X@eE$(7o;6&CQ zeSZ-?uSHh)xqPK}r`^-4>UDSq}95repTv2mqCu)8^!`e4T?N6sr+i)DURlMb3 zu?TfHs2BZ_&w!u$(3rCVO(8eYvfK@Ae)L2fS%t1DYCub8qPIL9z3txUy~5YJ$LPI4 zPejNS(Q_b1|AjL>?YY$d6X*Q?gqYYQi^-2ai^;fNFF4%PjGq2Lt=d=234JKaJKMxSO`fj1 zk~qt}pkb$_(xy$R++&Af>Y?#dl~tU09xI_fRaIj#kr zF-GWmKa}-(m%Q0wsE(}R`)=er{@~HW-6s$!#_pWG=)u{~OrW!q=m%Jjl*P-~4?>|SbYjkm9}=f>M$F>94`br< zwhLINWk`zVym-EdcYD~o20IZ0(Q&_G&HYPESP<*3KfmTU=4meG_rCC$nMZ9%^1lLYpC?i> zh&_HvU%^K9r@LpNYm4Sh<`QUcSVFUln2PZu;xDV9(y*4Z{?}9Uh{0TDrtSe|xNyJx zqlJTsfEtttB)p_nWVSC<-%o@ps)ct6ju7Yb zhH5JR+3O@!J6MOU6QK@gcFJD92U9LXdwDNXQu&;ZAeQ)$IY2|s^ZA}bo{PGwg^!T( z#XM+3f}km?hsKq>#HU+%ljjz+Zx%p%L;Om2 z?0G}a^4@_Z5=%JWc5$9OuRzMK70^wkhi|7P(yG-+n|Kzfm;~)jH8Gb^at}+OD&+I% z1pf^F+RDC9yrpRg61(~P&``%y-v!MFS-kPJl=)1)#AqVf-=`u)Nqp0$45@RSkfNi` ztk8~iI1d>Ue37-{5}sR_6GtJ3@RXiA4}aus@Fw5S`y(4KBma~d`7^tCn}k^^(}}OR z(uXk14n>Y(D7NF%?wfj)ZQvaedg)%2n4|KKY*aRI=4|90F3tc|pQUrQq|fz>HdMdz zL(LrCqIBTLX#tL$}fCS-x`FvMYmD& zJ2NIW5!ZRlXTS#!QT66(W=$+Y^$dCd%R>1)U_IWuf$I23)NGl`9&CZSFPUw<>l7N+ z5|ddUNWOvGUu`4WUc13w%h~N>0Xkwhzh&3*e&&62&0U7>ORLd4;uQKaoJ22zw`u)8 z7v+R=7=4*{3N%Ri zko(g+Da?!xhUR_Zj;FZS^_mj@=lgwZHRsKTPSW(^CXH-vR1F~R>CRH%8^iC;eJ>_95aOd=}RXhz0{C{AU5K561r7eq08e( z>UvYC7fX6kfy8h7plaI<)jEFP6Iy=GIl#}H_sIA?<5-vHl+dX7XTaGv@q`+Q-!qdJ z{PXxU6xxTuNRG5XawGRYO(RkQ$uqv^j*Ll5ku~B9vVWka<{eXNGRc`z%bD92j=ZhG z% zN7A{+*If92d}cOIljek|ar5VrfZQ@?An&l9u+>l14)E zl_a)v&d$!xE@ozCX124l+u7OK*~QGv@7eeF#~vo^)_H&4@AvEWR-+qC|E>k{Y^COW zxZ(BiI5oW)r=}{f&a>my@}LQ<5q;%rHEO%;1D`NLZJ!UeGa>p;0 zt8|L$M;}zJi(IAA=w>pw1HGOsDe%17Czh(#1i$fHf@&kX$T*`ud(Z=K@W?fm`E(5V zo-KTR2c2rHnyDuIe$8pC)at(nK7N_ndLD!;?5#@|m+SIpKDsi?kMjO8S!AJjg8omb}fDb@- z5;mV2#4FdwY4|;aKXv#X^r7IU^Wf*oxI?xyA3oy^!*^h^;p;r2 z_u=hA@f6mgXLX=kO|a@Rx}Ns$)`HI-(WQ-OQj4alJr<3R4}bO&&yIg@P{t4Fx&A#G zUI4r?Xo@WR=_>)D;2K~Y%eO1jnJnwqF<|h#&MV*;-sCm5d;~jOr1Edsqk^mXIz4uT ziWcUo=oft3A*JNHPF2APyaSWwD(6@)W$(7gw$xv?q4Q*m?JZjo{lXI=Tje5U2Q5?f zXCZKI-v4=Avp*PvUNTTwXP7_x(BB^4q0F^`aBH3TAJCQ5i`g?-S@(^iKlWDE5#GOb zuB@?3mFebdKa>v+I+hu6A3Gzp7nmmN%i+ojWTw3v{%;vQZ!es~5q!R1ZBgEJ>HNA# z_NGGF;hHT`ta8f967 zz*G6yU~~3w=<(;MJ${C{57Ji?(XPBY8vpA)72mKE?;U*kCn+icvpqKqUGxWde}2Ye zJ_3#pzV7VTXjtl|^Jj4?{UcdrPYl$B>P}tUI$D)au9Tx?rJQkOhvgNMSCm4A!#{jM zuIf`FRKGtEU1p5xKViO{9HzRnd#Ssns{a^lV?&4FMNPwx$@9k;dG<%D>66219u%aO)G)PL2CHp#h}zQd>1ELmJ9ntbGgqEi z@RVf-@dp z6;lh*$;y5dUDt>j*?(9f`(o-44ZbaQ829O@g9q8Mo`YZbWtA+8;V!`QGA}dhj&sO% zQyF{PHu}zNS+}NAYv8BvF>`(d_qPc==nG$NCy;47Fp(Ob$ZWVt*&oi99h}6TI955` z4v}97mokb>rR~&jzh2ykhMPneXLf?x8S!dnX>Fm?9RJ?MXiie|5X9hf5i#Koe2A!YO39sPL#lCQPzYUe+-V}OXfB1yu zssa0`3*M^wCz&N{nAxtIsru%ls?P*dnMbBdGy3S>+g1O2p*N^t15g=(t;$b-bQq zYPt>1q1zlaPUGWEn(!;XvlZs`?RsRp0AY zHCRveZ|tfMg)=LoYs((S{8_Ae`*t-MUL=9Wt$O2e;Py3zeFtI<8~C8K-vWqMz{ z(S6B-M)zg!8r@fykeU6R(S0_$=c=`a?@(sXu@4(QlYd3`9Iej(;xlZF)a4`jy8Pfj z*b1|9`a1mhA!@%Tj6UeVAAm0>`~Z0=foPt+vgF1oGl%`Cp1$-T9@G^@vVVXl@6G__ zY{$nv5B#rs1iM_J3VF>I@#kmu;yoRX?xiF~MJJL}ShEQaD1UGNPUYM*le{A4v)LiC zT@NPWWG`9+K7T_Bm_PI1{=W3TJ+cQ+ll?L^fzRANz$V+bHDH(X`C8!i+NaX%a?!iE zl{ws3)_dtSF}|`z!Go-)-<$}Obs5i!H<>dBS(!;UDf3Ae_npg?`EH1;_s~B-ouq94 zeadz%Rd!n$J|}u?^#s}9#d|lE8F=SZIDPar)UWAp{DB{$WorRn3W*^pkUw|m>_l{@e{&c8y*Vn2o1pTS znKe7taSvgz9Q#A$yeEM@kKJh_^QD`44Q$sH=r33JLb-18sQyoUuZ!60;N$DPDRMmm zo)}#r*KhG^_$)x~@7eD*#md7Re*-_h zX02MTMX4nx8qDRWygu{f`LVwmOa0V%-v+t+g~*l4e)mTSw`b|Mp_5ei^bpm6#a8c5 zmGfkzoD2KQ`37(Msk`eYse1GXRnM5G>SX5TiDVE?3|HOG<8m#S!>#sBa{tsH$u(VlGIi|OP6oohCgYjI@(RTa;ipGN3B#>>K0x5V41GH$Lz@td+j>z zXGVhaTk!S`_cMH6<&Ng()ZwPnhEGz8;d6cgS@hhIel5i4e#a9=_unkk+_Q$?bGr?{ zS6qhQ<2hh4_Zxn_nLP(SYWU$*_nQ)D_$>sZS#hV~_r`~Y-@mw#bnZu^`{Vcz-XCam zTk?b9bLV7TWwyTZ_d#`RXRbU??cI7v?MvqB68PGsx5+=Lnk>_$?J_6E$@1PFWq!C1 zOyYoSBkPpyv@0jE1B_s<^1e=2{=JdtgO{ryn|y={?%$Z#=!|ESitpz2@)t8^o)cYE zu1+1Z>tt#uI}|frL7K83gwtL*Rkq>Gj+P{4-5Vt`ip7;}b z%ir7ZEsQt**X|{H-cGb))vHwX!g^KTWKm7Y2-VH#V27Kk`r18U z_-o{19(27*{{w$;JsC&M`KZ3*xayhjT`$I|;p>BPx24O&_v6W3rKVVB#vSZ*gTOPd zi&f*)cr|W4D9`C(@E4>4o@VUnBux#4|2-wuOM7pbbEzp5_5F~)K)vwDu4T}$u_w#!+$Sk=#y$Mj09 z>blQUy=$==-dZMi$aJ}HBwN1gC|o<72d^{lckG*w#_G~*N7ViY{@KJMc$LQK%BBc) zrX}j?L+j8(maDT9ZKR){u1-zS)e{TV^&DAf=O^phz0~IXP+hwmgQoInquVdp%$bXg z?$7lyx}PU+e#T(K@4#rouXU8s!;f9Deuv?A@)5(YXou0mr`+iAICqdXkyn(7H@Be% z40Vsuqj`|g;}<(OT0SUQ1HmJhb>j!&ERAKW8LaEK64Ebo)~3j9sM9be)Y3 zRq;pcb{))8cu7trlBpap5-fd{a=%F;*BPAsxjFQ&SlMomRMwVb%JOH1JuwIUWQwwW zs*vpf^O+O>>3`j_e@A~@&es6$kXaHY%g2LcZik}_DPZq{UwC^Mo;>z0FSYmQQDt4= zb>ExW(mW51CC?5~cfBW|Uz>o=nYw+EUiHLmWyex;=is@SOR|r^JDLaK_n}V0ie*c+ z$(qRyi67Kda!97aY3QWst)Gj&&m37>4&K9@JS<3A?~Q|}m<5*zuhB3B%>aG*)d}R# z^Kn)!gd^nhyh0}0A(!%&t<=d`LF~HttM;d;kbSpsYp@FQ4(RlC-12&noZAHa=+F77 zu$>)nK!;8@rRdC)hgF=8o@HF3&Q;9T`2u{By90G`#1d6J!EN)`Qsu~)E9YDElaU8h zy9d19TA=zJHo4I6xUS@?;hEKHh!0i6Z2F)ToaHhz+<)-HK2xfO@?-2|+vM38qo&7Q zYBJTSDINXT0A{*w)Y6;ix|rvid=urpeU-f6$;}U~RP)oD+*XgyuRbmX0iJ@q$|DPA0BPjm9^Z`#ADn!8SWwJpgObm>8kwI^$mWx_f{C) zPX3DS3k}+5r@&(_kxewt=y5|QwT1RGE6nI|^cdOJ{N8%f@V}efrQhE)dOlKT^xSvC z=+!g8=rwj8o``3Ro^7E<&*%3V{v){A@+dhE-6rT-{vdTGvhVc^RQqJ~c8{SsfJ159 zz+Uv!XjvVjl(qAqY%`e!D|(~DKL|Egq1=zQDL>DvQ^q=-o-&tQx^fl!Q)`do?cSKA zbK~)m{ZXLex~(djN2bV`I-Se`BaBDC@mjKSZ#pFVK3>}!B4zuN+UvbsS&PwM^`ch{ zU|;$;Rkl&|pWf_U<>`!sjKfL)Uu%2$qWd14zedkD7-r1(i>*;x^^ssqD zl{Ih=y%Wx16+7SucpZLdm$_rG%nORhDkH;s!z5+iSpw#WR=Q%Ba(crl{>U?B_I%mq z(w`=w{H^uEp_b zot*s~s{Swc5FQv$juE{kVGK9Ucc|f4X0*-Be}P`Pc`h|nQ;RXn9Od5szim&1b}lIK*inijy%Wtrri2*#1Xj&=V*HBAks z*G*C5JN|OVOp+^ikm_&QuDaXFFdLJux`Xts%y`uWy2vSAt?E}3HzxDePQHt z%}s-|*mGHEg2ieeRW~-%d4| znSEy-QOmcB)%u^qYWoPx&=`VG>A2dX(AE^K(d9_=B58P;O7^Iu7u?#&zW50z@#}VV zRPR;iwV{U3&3MHB=xzAcWP*e4H2iLyjV=ZK^Q(^-{=K^y{xA0>E9{WrzqZ=&zluIO zkGvOiH{-hf1xD{hj~l)7(8gGAGkU*uwnArWf3js3LRN3B1zA3d>F*hp}%jXR<3PR4$u0$8&>J$PeCdy9H=vg zxLI{_na{`Zvf+bq?kU-oLM)9e#y@;`k=C}`OCgwAeytq$_b?B)uN|vW=79ns{HM6 zg>P(8L0@X{T|DY1Lv?EMWS!bVE&enWZhbF#)A(dR*rg(RO>rccXAg@?hdaqy8KUyI z4qeP&sER*2RQX+BIeuLzC%A5PJb0m(+^}cHFyk@r1%OvF*LuK-oBB{U*MZBo;0K~N zHmpff!`VY>yncr~)7Pu1$4L4|hng1h{#53;=0);+$^7}&6nS`VcvgDknL-bI5`3d< zu9`k-SJO^^HQin)&%*=N=u2Hhf~7o7R_-CVx*O~vdH zb&i@_nFjYCr>f7oRCRH@oDU5qYnR^lPu;zATs5mlsMd|fX4EG5MtBKo)ivHL*X>8a zI#;RTe)Og9!uL1xbw{t1ciUVw{}`&4_wcRX+Fz}|&xOw~rUtj_QuA81KaTEY-U7A1 z3y=L8n8wXcwfll6ddOsovau#_G`_Zj@ zX7qgQU89$FQ*Y=vADd(J{d1Yo?@TCwogU*l^Ls{5=Mkev^cBPR zgBx`%Ws0s$7^F+}+_bL1Z`{{kEf@R39KibV zvVbF-)5wIFUyNrK+-CkYk>Sx>+Eo;i8+Ug{FB-wM2dUA<)9 z4WID#H2P|rY*)R?o<2^tBO{eny-r!<>8Z&v=y8_FGA2T%6S2xLlST0luELCFzF>zF zWZpgRR<;k%jFwU8GEJxU3^hU_;Te-Qqy+HieyZGFs~(o?X=55A3R+v#WM1 zCIby^K~22!8rtye&C|){6*}o6v-CaYgCSrpYs+=&2Qbgfr7HYzt4{yS4NC`@i$TAG zTPRNA+x!iUV-OKarlvrtuI{N;QlP1XMa6Ko~3@0HQ$KswaG=WhJUrlvSN zHgM0)bI~||PA~IYszw{0pdEM;9)jyGLl4%QTC1VPj=~50uwI^D!sRKpGj9%&r-l9P zA!bQ-L$`gG8lRIq5oPk!m&j9|B+o5Qxj#K1*OX$_wxE?+0yn^XUo&owYHs7s(qiV| z*q#5&#jje#uaS6^LcMZyp*8K}qpIi@Rb{24aT`g`1Lu6LPBrhXRqYpbs+&*NZsicw zUss{J8n_Uir*##3!AjA%d>E~Ub7V&n69?Y7_!bh>VQk?@`v~Px*vM$m*DfBGPSXDc&f7MiZL++u*^ zTLA{zF9ts#JV3CYvf{>br^_wdKt9LUW8n+Hb>0{R*FbGAqCbx5E7N3pon@9XPR)_& zGkozaTa=l%OauFDpEL)(z9ByJdKVT4WDmPRL?EE%MUy z#^Q6}nG@ZmoTr($cc2IDaFR34jr0xE@aDpMzRNCr3qQ9H4(Zfy-0`|`p9(wnk)iuf zKH+qBW;phb$rkH2Oc$Oc%i+{CT|AYp3ioz7=B2CZ{4Q0)`PEEIR_!wMAU61CdagS( zTAl~<<$a+|E#u)r0wsuv2widtb zTvdlok+XObz1X3uA$SIV#ZN&lg@YdBs0k$FbFmzSL8=-~M*e1hRb8DR=Z8M<3#(NV zvQ#x^W~+AUHq~wmQtgR$_=Ucz&E(g-QK}p0Qk?}1$#qx_PxXguSfs|1E_r?)fwyU> zyf=?i^HaROlgV+}xJhl-!}WiHmedX3-d$=-4^x|Gvf9U$spGo?*BAf-lt}G zgWN-Vlovlvd0FewH4XNT;&;M01c>+l6v`Ej03-M&mk&x2d;!x#Pd4xO8DNatSe zuad{6;OR_N;eW|Gcm+|e-RtubWD4aia_b0v0C{UJ`;|t<#;}%)N*b(F50$1a? zU2%;3&nYs!xQ01187z}Je8b9&dsx<++1*~90-usXj~=Hii&IwTJU+)US>xltRQ%!b zBG8G!|7B2j&++|EKZ0*~796BU*&X~mf;TI74;~@SydY7nkeeQ{;wi8G>HSBKU^S$)D?Q-5y z$gDY4PVaVAza5}zvsX1e(SFm*Yw*$3j*C`pLMk`Km_a{9^POW=U2=fx&%r|qEOmj8 z8t*@##e^3QmFb-?Xh?cS+t zH@0x&?=z#@n|B$$t2Y?k|DIv^)i{kF`z{*(ebNm7H+LI7mvXcH*#k!38?G1wp8VJd z9CDJIl6{Q8;u%K3o7dst!>dQGwg2zj0W>|yoz6|V`mRlvH!f7$#zD-L;K6xl$bU#6 zYld5&jr6+Y-sC}&-}B%e<&I|u`Z^K64A1x#>}3lkD`$^|+u&Vf^-NOXrQT#?E!UZW z={ge@tD^D$_{)|tUmj8J)zRGR83Sh!FWZSDvN?__JF2g;^XJLldmp>cIxvSeWjE3f zcGC+#+@PFQ^n`yE$~KSLvUY;3PfkYHyja$VrLqooDD%KdS-Rq7xjh`LBt+&L!Ad4Y zlFtI(c#-#iT#5%5PwjDZHGECh_?76ZkMpsp;Um%9dM;Ad_3i9rZL-eXtIURYJ|?~K zarlOJJE-M@r}wAqF|w21rPbD;5+)>QLj$VIsC7@WS_6kM=4Sd^>8hVNT{ZR8-QZwVx#p@WfE>?=d{usnz9za*l?lB6=WJD$x#YM3 z&+eves(KV$(*uw9N{pOeJLUXtpq%UId-LYY88r=m6Im6HPE-xvn%e8(DEEWeW^m_d zbEImwF6G8ps_LI807Dw4hNSg!Pnjq;d_?09`{XgNQ&T5Crdy>JFpXBL3k-2IITd(! z*Cdl$;nwB*j^YXMSLYNTbpV^Nb$zM;QLk z=HuOa5e(&dqhD2;5%4jYEIWTO0?v`$vY5QF+u7^(lKZ(FUVi7R>e>gdSYg#A(+;)r zOl@wCQ}Yb+x$h@iYH6HWZd#z6>Eo3gS<516Mi5ocRVin0g;HH`LkeMamvjg9l@@Y+dN-GSigZ~Y)pemeaB!QJoFiZM>6Be$M~CaYlN)(Ei*&)g zk}SXZa@?|B&Qa)9LRwVUYpLo_=Br`mA-Ufktj1^QRg1tUt?6o>kgS%eVQTp>QZ09z z)SQjJ>xYSIUbseHvqMeEHhD&r%iYB5cCH_u9I*Nk%##b(@!z(op%5N^S38`*Y}H@E zN3Zp%pyEiaupGm}NmCcTh&i*?C-5-xSO7_7YXbO;JT9cN?nas4{fED(_C= zcFHVOz8Npaba=eCf~mQ6ayA#Kx_^b7zvCV3nyRYNeq>%xR@KBbIhmQO(fHK_g#4SgN)@ z)EJzZH+O@)GpEY?+;(}-lV`&$nH^@4{c*6&XAYt(VfWci-?MA7rUx2ZnipMiR zk4l3>-@i_|Pn+Nu)+=`gw;t`(P^%NHa2Gn1)nE-1$yLEWevRGfcY0Si3JPsleK6uSYs%8xzlAH z-Wz^59bSLoY%wP7{zOp|sRoP$qQCsxA zh(w-AzRJ3Z8S~M2Wwp@TchciWlyS?UnCA=MW6&(_*LE;_j#u`t;JF$6+)fQtuFXjf z+IT#9_+r62PQEpY8)Do-F}3UDYim?c1-ED2q@px5EKAx|61YI;R^d%wfmh+ST%Es? zPR{8(GLG1h9)oiZ$NO3piY}VEGapyO=3#P|!o3~XgqNHhErx8N$41FpGGE>X{(l!u zCp%@en(yG*`!BLooZIB>=a#23O^w-j6hhHA_W^r>t96&6)9Te-9!Y_h{p~*IqNO`)!%gtMH)FlU!@R<9BoK_9gtU1-kO~BDEh#Q`=H< zo}NgOcfnLO8Q{c@<7x`ZRZ|r`@3T5y^Xyj-g~}Gg^L!(Es~3HgxeV@VEU(wCA@F2t z$&@SxYphfD6LxY<;KIIhfklEpR>jCRaU7bBRm$8#UkcwR^E*D|7%>|jU>`Sk00heEi>7@7AZ=$ugqm zcs6Gyj)S8cE6Y(nR^mW%1{0L=#!+SbI#Q-{a1$T#oOqo6Hi$W~3e7;YM>&^9DQELw z*^|e?b41DZ-5_+>@by#L!CUzKLtA94rO(AtbLFeipsk0ih@T_Kt$F_~6q!2iNx_HElmY z&GlCOeQ41_538OYQGdOU>b~8o+K;GxFal>dJKUvha!kM*|I=|*PT@Y$^k_1e*5aX< zqDpq|ikgY444NQE&rVg%E0S|H|E?9?%C~b=l{QC?*mhN}aj0@CIiTmkI>-5``sQfW z)Re0B)MnNG4!-gf@5g&ox0tW#+lg|`gJ<}4uv~wK%H?#zX>60b|1dS)n;=i&KAtTJ zU`q4U248vUb@sZq=Ie4$k}iY)v{zDljaP5^3x?m`K}Po(PNUnJi|TTt3+omJR!Z(u zC?3K$obtX8-?Ce3+``ZG*$Ha=k!Rx13*=2ZqRhil@Xr4DvBEmJXKn zTp_)Txo|@w_w>?bemEISC6ayzp80gFET1h$YZHh^m$~#FI0bm(%;l?KZ{zP2R`kx_k{!amD@n?kpuBB zFv(}5WFHYI+huC5tqmV7{kV0zY`f{%&(Y7@Vq{-6ME31y#3s@o{{%n!!lrD;1ld0# zhvg1*swT9pG5FeB`z!AQ=FYaU%Ku=NPQK8W95-@!kHH_T*`qVHlXbS+L7lzWqLTjx z>f9HB_!2gg+rCNV1>_k$FqFH>p}bzpRlOHXD^1Ea>fNk<@ zUW2cIoo6SrXL_NU!pYIg-z)Euaq{NV2eWp``y%)X+Ww}Mi`6u7i##SU3iE8am!f%j zgx&6aslIiD>f?HIUnoL#3l`uvNL1}@hxz>YlJJQ**q1Apj!@-&3so7ifLjgJTYefd zr$>&|bUFUT-IPD4s`9s8a?E89T)IcjM+4-1ftveIsvH}cNB=XPS`Am_*ZdnL(ErAw zWoc8*eP}n^;NMF^Rr4{wW`?WwApF8#?6tFsRlk0*>JKq<_8%%&(qc8d(p&CBo$?%( zysf49~p@ip`}y1jaj;WPh=u6iTY@fEsDFu2xxndATJ zkazY%yj6THSGLG~iyN&QnDDHz=u8KzDUsLbDCVm-c+HJ&Bd3Uduzm`jgoR{fQhT|H z$_!Yh%;c$fz3sBvC(3&22>odrnI=WNu7}7H7c28vaEU&Xl<_h;>qSds>QB#`OFiDR z4y>Ks$mpauFtffGD(g0|_%{N`iyb5D>u?7BcQW5qDD&(Z{5JHv{mf{VE$mAU+47Dn zYndjQV4CE=nHSk)_T=N$rS6u2f#lTSL0Bg%SdcY%2wnuYEG5yhe3vMTlU}!; z-goa5S>GH%HWMGGKr%nejUo@6L0AQM!^o7s@((|;P?LVvzBf$S&6{NZliGSQK{?A4&>XCnJ;p(%JiYFPwdlMP zmGfhVavz$8-zZqQrZ(mEwkYo#^8eN!1>a22sn&_)+wRhtY`n4lChi3llVgi6dISCL zl@gVnJF2p)+`4;ym@3~Y{vv=Dg>=1_W6liLH9xt+W(c0Oi@ zhMnk9=(R3if7M-@sk&Qcsx}Hv{5~+qclXJW$Xpph7R5&HrhGe-`I7pYUnj>{r>bs% zD_<7P{5eyOuNKJh!fH8=j8)YVIEF$rrU{Z`?J@eCU6nVYJ6%s!({ixR2{Tnyi=OP@ zOjRdNQFUFAst=Z^dP%XW_k(jTq0f&2>r92O_rjqp;<+%`tNQ9_zSg~RZv@+Pb*Xul zovhFRwHARZCXs3V4l`iCp?CuP)c(*>wWEu=e5;=>?{ul-F6wWuxNj7pjvsx{xx|u* zLSLLp4%jpF$j8xu-Y^c`XS=$oe8cjGddj4 zb)>Arcx}HBj~~rM{z9}eZ{H@%C}zJFUb~qymEPr0`X2+8USFh)RVMIwlPq&R@LVO} z8{3ri$Z>EF@cSR;(2Mx>Nj&UN@pk_@K2Lx6ZF-Xba#>5LJsZ6wHk?cp4_PYk*ZDKa zh%b`)ZF=38du8#F$(Db(*>t(&0y1o8@GH3b8vZ+j#XVFFHQELZ!%esj9 z`6E7$9UaOyZkeB$EYm&oyd>^5d=n&NjJ+ESJKIs7;~R!;pf<=m1e`~GpV*S9GrWvg=E z4^dtKSr{YcDYqp;xgGH6U-eRcPA?UFQmn$A)ZLM_DqgWgXTRG;Zo5q-Ppl>Tk2|n$ z%;fe1ncm_4I`{zHXOi z6t96-tny4l_XYpqF$JMJVa|M-J^#>pdB3LTy@PN3&r~(vMJ8X*fnXAG^6cHC#$;+Q zn>@0U=&U~*rTRy}IL3si_P*I#DbpC;EPzV{V-)iii9 zS{CvKCy|R*g_iR%?x5^N&+-qp*@sN*=a%Xc7*2a6eQ(`fwQt0$x)|+pAACBmEl}H! zXxbf_Z{k(vR79kxaYUrL~9wa$@4xx z`|H4a)=Y-ypDougAGrpD2|qDZ4P($QJf12`K)B2~>`lx#=5;Ny42HXMA5!K9aEQVk z%51EV^)|5mSzBa%YMwH?Ml16*_P6EmXyex@!_8gFUfeuw$#OhanNxWlfEijpn=R{eUS>?{ZCfxkzgU*G zT>R(M+$%+V{-esg1w9x)J5w3?)c0^Fvvs9R|AMb=_3SRu2S9#WA#}$M#ap{XXgxL#&oM>I9cvL zaWiPoV4eThF_m==Q~8JFt(;+Rx(%#8B%Iy=#xjHaht2y`brS8$PCVV~LfFR~YIrD0 zZn!K@&j|KCcDL{Vd4{peZA1h85gE%jK|dP}Q!j za=tf6PG(l;V=1ayG*^z(L2?|Y{>nzm@lA~?PYzQ>4mz9%67d$c%P}5orelq&!^pD8 zi@+}%pc)@A&)4a#tI$=a|C1jI=JH8zRUKi+y_tF}9l_s&XHVFyx+-?Q1;^xmZ@ZfA zFH!TK^VB+fwOZZt)fSYjwg9hM%keYVe8|KuLDyNQwlb62O5)(xsWT6G^$)_kZ$SUq zwMES@j*xdN&!kZ?^6rC&8Q@P1_k|x=smu4{9nWD-y?Z2l#e&l4;|EsTDLlfl=+bPoz>}8)6%CuoBw+`xLE+0xRbQ`=iJJ*d} zV3Y@Cjhrj%dr8cP;DZ75kf+zlGOWK$Sr%pN9j%O!!Q2BHq4f4CO0URO#(=%bu$9Wx zUJ4dDL7Bdzl8PGnI`kv5SioHsZxf@?4{P13`Tp>LhUj42Gd`s1(Bt)RGFQ7 zl=%}q>U5wiJ}#LLQ8(rM{=xCGY)wZaT>xeR_eU0H*2F-#4;M8)n4C}kj(0J0a5B$~ zTyhAd%+I(>5*-gNgC1oTeHy*EDZERj^z}S*j!?_ge|!g?ht2eQKJU@l;5>t6`}u(E zKJekG^gsJD?#>>Ty>p6kZgw$Of|tC8&gZXz%C}4=ABkLtNc!3nZsooOH+tVO<$mEH z?`XX8&QDTa$tLA34^>_ae=l&NPJTXCr(WyQ={v(zv@%dD)z5eMHxF*AG@mZ9jdmXA8{4R_0R@- z9=Lk!G_VqI%J1Rkeuq<^j;EYi!+XzRdFNAmLwH@?ZI_oBtvTbkn#;ifd+$-xnYr>D zM<15904^RLZ}A%VGW-VL6sqo#8r5v}k@HN4s=keuqmBAM@29Ez{r&hO3mE+dss+ydb{tj3_7QT7podnL;>*J?d&fc5 z9NVXw;-jj02<-FLL^%f(t12c^4ky^sZ!6`fwyG*;tg4^&Q*H1>)q#b&7NDR02wiIM zcr`DZfM;+W_l{Po^+<(U*QBVmKVCxinwH~HYAM6RURA1=uiMqqGhfY*qj}l9Q{EAi z)l@e|O=|Q?}Xc`=RNG=jSb>|dXw?^;A1uBT?x z`m@7Ll4U@E%oge{pB~wlnqM*p%!j%0qfN}d2k47!vh`Udo9}96P4;D`Tq5iJrLxA1 zgcCd_YrpZz3_L3HS^C`Ze3|^Y#dm6^Ot(;fC&Fdkw3_Eb9T}f|U%%6fnbk9g9pz)M zmM!1_TzI(bUxG^x2V+SZtegUia<1ZYnkD6pL09nhKAl_!H)q+Plg}n9e*xaRm>A_A zgFpB=j2SaRc`IX-x0S!YY64td3qGm!I(ZGR;vds=`qs@lUDt-E13%SSZi&5vK9lEC zNpG@Nsut?}hp8%C>B4tT&e53px)`@X731<%af`1mqEo(@S)qz_cCDvua!%f*niMqs zcT85p!vp1hBTeoKt&+GBq7x)?A5>X_`&Vuk-$q zL25b#|NayB=%;ua{tZ^&P@{&#eR36$-}1pA)mrF9{mbM`XP&%5%~#Nq;B~7v^4|-| zSt{bS796VD7g|)^wOr0UlT>9XMWbn@KMz$UI@U_+vEtzZZnRHS1v^9Kg$1g5mOhq@ zA2ugaHK%h``zjwR6g}L7%$+yaF}K#BKg*W`9>(!lsbyi1S_Z>o7W+__5%MK@j2>)MCbf43yYcxX5#%wjsM^pab4mHjf zGeWQ$hIOgFs=w;?z<)4<)jm2*bvI2>y~nN-o^+)zPEy80cwp}Wx43<+%;)@M;q_}- zl`o5p{x->`%p>ezwadvbVL$5)=Gm8i_1H9-`z%J^m@9L5kxUtGnV8K?lLKYiv|Q$% z;45p|Wd3_QcYs#P90d0rXCq4~T&5^$wG+K_JA2hB=GVKZy|Ui2L~W3{YLd*UzW=|^ zMU0er@;3b6gJrHvmE}2l*~LI*E}(}smcy?zlkS@U?*NB=A_xy0e8ng*)PB^XV-tMf zG+AeZx7^)JmSC^U;_KV7L#CNLGoGUMw#}CLfw^#ceB6X~YA%9Vn3?z9UGVMe;X#&w z@mRq~*xT+|tQ-sYNJz1A--%OhdzA9Z$wg}ItCLwf$h?o!$>Ryizu>351B;cLzJ~nx zNy`1?uyQXPW4=u0b{?6XgAVHC1Kch-zFLJ7f_3_ySvtL9s7`+}Tc@qVRP@$#o%wy7 z&TjQ*&P+qI5~i{Ua%~6tQFqkYbMWW(Bf7YDq%PhTr;C%hp}>q$`Amtb@S9h!<@w*v zTzKiI8ivQKAth1`Rn*@S6TZ7HdG@TugFsK?`}erw@Yzj6Up!ad8Da9q!a4U`uBI2* z@e=UBe#+cd96~O{KDqazSBbFW3*ck_l%`sqnKj|t6gf{0bIozrD}G9 znwP!sBvGo}a9p*67pZ2<27K!DPJG;sLtr$^z2KhoRKEb#cE^)(Z*$q#)_f*RjQlDi{UZg+tihnK36 zzS+2rdEu15JpW#(ro2EkzrbDU%c*MPHF{}m-T!vPHuT9YzZHN_!4Wuzs?t<&0&fwL}|Jfw-Uy(9*B!a_JUwhN>4OplVo6MV;3Aa|@ zo#XE;pbjr5$P_zJ8C|?pk?As#8uw>M3xTWX&JH)t$!--)f8%pz@NcdQm1&7nrcsMz zT12gMb<*>g1JjNu^L^^*@=94J$KY9?LI&7jaK?1mn(Xk3iON1PU-sK(f$uMXt6!z8 zT|;DjnVEDW&yN81zc2Zkuh9RZ*zY{lb5A~BA~ip3C--c5-sEgi=3M$_R0LjjFc#|$ zWuxb_pCDU$!4BmV!Non#%yJF}NZf{ji^rvV0>EiO)x+rp+{sI&I`H(8J&`S>-jh6x5{CB5n z{;>0!MIZJUb+_Y~s#l{a3*DxwTSMh&WY5cBuYH}_`a$>2l{5;&@&B!5FK&P<9Jx!L3;g|?CX<^@M*R5wX)21@^|Q0VN0l?Hf2;# zSH=lGPwzUJzNY`p9WP7dSo$Y(;A8aCTMOYY_?(4&?q7~8WA_+kJi0>}pWzpHn*YA0 z9RJS_G6n0%Q%NKzlil-cG$upv2ApFiOrhrBUhO}}CSBonaZmxR9^0*@{bZ!PLC-6-mQzz&Qk>(pJVViIlHq{xd-Bj16v=9?&S5gYIuE} z+&tqOqZ0A7u9An^@Qhuc#w!kaZo~uhb|f4hGuFuxd5YlP>vHi34d%wka(T{;R@0r+ z)$}}?&dJ~{Wp*`QJ0$lLF4g}9mh+%Z)t8trH!$n1NRbQ8sQU%5g;(ITOSY))%XO-W zDwcBvdtFVGDz}28ycMB~3l6FLXR@*5*jwv(cH}No<@M}&U!v9Q*w#x{?ku(4&F!&I%GL5tESYP^)C4}{Y31X+1Flkb5N&G#x^Z$fmZLow zvJyXUFM1uk=b2R1rGrD>I#ua$b;=md>-#bp2P>HWmhv_XoNr8pOz{BYewvdGHGPlx*(MDLi~OXexTGQG4*84dX| z-C&Vv53}9#vD_A#$iItKW;c^EnV&O556DtVzig!bKjK+1CK8NvqS8<9QO2QBGAl)y z1MJ|IU?scZ>)v+D`VznYAqqVx8j?%C=p)N{y@O{==Jh{iqVg7&E3aFca(N!){>V<2 zD(~{O|I?UxTftxc z6N}#_N=*^;!JX6OA58zV6;62ALMT;_$`GJq|*9f`03h_zV)DS!jeQSa0%D`17 zneY~FR{f2$c})i>ojp3kw@Vqk#!Y>TWg6m9#x-`eaOS}c>|;MjrYHR1m+4i(y=B@7 zM)K-5X2eCZ>;R91FUlOaMwuUnfG2``E|@Rty>2+`HDLPG(dNTwD|t(vC(||R_|Rss zMX-!qut)zQ^e9oXTxHhm2L@BzA#?Kvnc^2HBP3YqZ^2J|H$)l9$uhIQWHQTG-%21C zDFU4hJ!v*Q?DyD(5^I(66&=Z9>bvY?%t8XX@1SzB*NQj9cW) zgp~oxLvv+sL$`JSzP#TsGI9@blQK$HKd>Y)L~HF>WknoT)*SS75tEb^lYkfKkZkYb zX_^Su(ru6IN59Fa- z*QL6U8>9>DujRdpRsIGym?x}M#iAlrn$UoZM=#kxe@H*ZZqZw=UM*;-@CbdFug0}_ zR>z=Uis$*fo_X^C{9YHl)662~sr71_1IDlwOyU-1t3Q*te^96vbXF}F($%sn1fHR{ zyxZw}OKQ~kYmpj!4y$emdakh5V5zgv&!C^YHx4aHI6Qs?emQ2VyBDjvH(1EYU{z$* z>0;R;)lw8_bN3G0Pk=I zFgtHY7Z)PWUw-oR7)_ml*Pdj)yf#pctLc#)^WnAW^VLae`fZiGe}KUpYEw&`uUc$y z3|qivdXIsVKSJN6Ha+agq0H*9O=lP6<2?ZO5nTpF1bkaC%)k_{(eH6iIW)4V^f z$-HGzM@a$73-MDD+%OG7aZ*o~FJ(gwy*pRaW}7 zHDijbNhxrP#`Fx@dO9#n*|3LCH3(!W-#aG`;S?AH-yoiqMmw)nBz*=TV`RB+MU4plK!FUyY zu}8)4l2LOz_kkMXb>3ywg_sCk__&C?T59c^p?Lhsw~gaQY|JK|U&rl)_v3Y81UF0$ z3{m-TX3j5$=;Dy6O$?h8m zM*#lT4eTKXjb`dEhI1*U8%h?CvNy#80!;La%S@W#Vo*9&W#RYMCA(?;LvO zhQqwJ(1!kZ6no27Ro{rNE0J0VS`43-tC}W{Y9rRDrosoGV3?fGu#bHjs*2=+D$gs{ z|B-YyP*IRu7bYY#Bs4QKubB~<5t)%0nc~*W%#4i8$c)U$%!tg$h`eS-W=5uDX2vx# z17>40*7!c8njOcspTdi57;xIG5=iTS*z0Wy~E5PsFW8rDp z^_`cj#=*nY_z`*Y$Ly{hwNuR%k)C%bxmn7re<_b^p|= z?l}|CNvL1vg8idH)$>j+xAsvw{Ce;Q;mIba?P?&_R_#^St6p`v$?d1&b!}p{O~Y_?l8bb1jZtSKb=1;+JR7@o z^g%SY_w71bH;;UCu?zugiMu%R!vGn$YYZ2s>gYM*@UC;@n#9n@)NkAI8ZS%6=M_w? zM85cXF52FJOp&|LsI73JvE-R>Fz&-L4xKI|9zj!Hl*}8FWbWR{&T=EW^=gPEV)hdB z^e*bVf@LxWPgKdpVB=Vaj{dk=hU-$%=;!0Bna6IS0%8uWZf3A7i^wfU5`VAv%fx+R z>f3|gGl6^)ekGB*DiYl+caADgz|p5nQ0>XXs`D`$W*D0EtTgJVY0NlTs`A$}WPcjm zyTD&w8834_8vJ?iGX+29c4E+;j;0cT$E=+l(Y)4Y+%lb?jL!{BJW7sx4}MjrKR$1? z_E(v4bX5rZEypn@bhxZR6PZB|9uD2AvV(S2+{``(%Wmd6xR@P=UKg}Lj&%iUz_08g z*K_Alr+p8O^=HWWORpNPi%|XE;c5toR>P4f6&TF7^H|P-mLStzKnTNg#dGd+FC+7p1e8_UrU>;0M}_FMTe15T?^-o}?CP zv=%=&{sj0F@U69?Ky4x5?q3_!n&xJfSUNqgqtqO7j#_lH8h>)hdE8IVgQMl_h=;2U zQRBR2;CO&sedpyiO_!%5Nv(NdYNL*8`{oEdtx;|J2CJ=^IRA_IzHG4CRJLW{vJzB zm!zHu_}yM>=jr-S+~9Bh9zAOHA9o%RrKmUYy*{Mpxhh8Lx>&ukO3qq5+v zMyhKuwe1Is<%`-N-vM&_oolEwqtyN2Y;bqCe7h&hH)9R66~UIlQ_+#hA4fZ6m`6VO z$`Kis55~t~lwl5WwxmafmBBK+#GiebjkjZ~N|sXBjhKiw9RzL>V;1}-PZKZy<@bVj zfN$iIwq+_g4rf@hN+qrEFK>ZWTO-(+Oum+pDC-cg$wp4sN-fxgZ!C%&@6vb~E2tOm zci{)?R>@`U)Rp|a)Ic+9bM|Ptm#)Lp z!7fQlp4=z6`Q=dc6!pkY@ z`4f#{wo|=V%-5M!e)?@j1alHn*=xQ=zx}dAy*?)%7%S(-09AF#sVLmK|@86*AJ=Ase z8`ZOcJbaH4ek4#mx4?^f(G`mLvr~L#E2pTZ@;o`}IQ3jJo&Nmg^em2r2j%lvkcuY^ zfBMNCa67T`!OM5O1BWd5(J(&yRO-$@56E|XxqQ^2C2vHjWF$Q0__Hc`o*F8dU*9-Q zh9m2lt3|!HeU}WsgyCgbr;=IdZQl&YczYba&ei0W@DQE&M)E!A3`Hu*Pm`g97+$&` zKGY?{$xSMG2oB~f{C_tw6UJFyu|?K+9>?$(|NFcw|K^-O?Um`P3Yi{FfZNQF@pG{4 zF8I;0U|`H58C_HGMsJjP6}`!kt(05jnGIlZ-*&`HU3&YWrohf%WrtvyTCeby*R;9mMJz z8x*k-%NZ&A51A^ZXQh1n0CgTccfU;2iMyHE_gkJCDjagZb_mZ$IOlY> znxAB@1M|8X!||%z4(|OiPtM2D5^gujwK5IOiU**a3SD#vTjsWYV z_QwtC$m8sBhkB2-tMh0qcYe6KJ9?=@#xNI}T+_iFo`ZJqSG`UrB;zZj?g@fhc`SyS z1>WzqY@H3mKb6Lu_RY)%F&)zBpa?W!w|p0Q)bXjC8VeomM?9MM1<36wQ*#XZ_iu2L z3wG0out3eeBWgai7fs(s-vNC-13S>ji{)zKy%=)Y_jg#0Pmw#{w^fa!3wcadV{@yT z!2jk~tni+?TBjZ$=EBGupUqd# z*esoTmHhOFvFe_NZuxFF`T)5obz9G;H9FO^hJGn@%^QfXH)i9B<$3O)SL)Ylb++!3 zudqj5haB`GpsD;k*UWA1axKjl@TmLrvwS&y5`H>)A3UEPykiR&>wS(Z}IsRxd=Wn1b)h?Xh}U=A#+3A7n3&N7i!(&<@TKBQ-Mn!QnJjqpPRMxFVb9sgPy-Hs(msTY3+B z9MX)cSu#y^GuXL$aIfm0td?VTfU1u&W9WZ)mD_j7R!-jP0Eh2cB+Fi6oP5S|4|Qnc zP8m0V+jc&K!#h>dbU?;IJ{bq^m2o&6>@x7bGfk#X#?xQ1i}wQ0I(#I3fRkmpbRJ$B z=7SEJFPoDd1>0(s_QtDx`Z{{=49r$3(8<_r)h{s;XIs^5^~iH6G4(-#Tpz$Y^rXsp z@hUZxnB|P%oZglr*KTsrv1rfHV93SzG#^Kcejy3Zf0kP4EolqRP={YE=giFvckcRY z9qO#eRM!;?)jiXx?vsH!MGwg-D;(Shsp`ocf;Pt)J0C_5ja#Q8s9Ubt%AS%MW|1w^ znR|Duw{*NN%#-fEz>V)sl=oY++V}g^`o;ozR!ma!Afw!$;$iD^sQKOfYW_BY7=*jb zH^{x6nV*;Pdf&~aj|mQ_8_jVMTxQ5%IX}s#$7z$CsmuBQ7&)80YIO9e>De-7r{OER z5$-ZHS*=kSYD?at_BZK67_=5|wx2rSnmewE2J6nD9j;b8b!&TT0JB(l&=1zp1Mao^ ztE0F;9j}9h__;cc7t8zZWO;iIq3P4R8$dmn0Ty|us(TZDo*&2`Z`-d^KD5qD-0C_@ ze*P>PLqEO2iEcbR+tgDQL@h|}aP2&G-^c94y{T|Gc+@^SE8oMb$-lR;+jfk)#|}~V z@N{MY6q93uf&1uF@>?rkzFWp|y)r(!L8hw<*keWQa)!8ilbotH6%K@$>#;J&g!tS$ z4*cP4|CTRP%{rO4kRx7*hwnM#jdNstg*vbuE+pUj|32y8{dDvK=B)uE4zHK#mQCcC zXl&Qw>A$&5win1X{}?Z8*a|QZZt_>2^Ez_HjZLlSZYCPz?%m%CzJEM^N7s9 z?7%ZQU6$P$vMwfmk27of^G&jR6wcnQQPha&hO;N2Z$zWNgEtcc;aGO6eDFb4o{3g9 z{Cf?#r(*{E{1Yx_2aQzq>s}q7KpcFRCHvkqSvM1J-;t;OK#rIJZq>)j^u-1lKS5K! z<(!NQR?7I!E*Up-2i+4Y<7MQXGv|S`;N2J5GLMS~*SRCEA@&w`zf@oQS_S+1;ZvMtLqW!pqcsVI$xwySHS6w9;F^Q)gF9i zry6&ud)Y?$GO3>i2CHK&zL@d|wRA0IZpwVQ%XiAXGDq&OrRE`<<@WLG2f*dz32M4y zxSF;Fsi|w5nm(s?-RM$d5czZ2L^WI%ullMGl)BIGSyi0p^Gf8s z6LV;brz7B7#t?Up495GDuWlnX!`uLphk)!_d5di48+Jdi96o5UdG4?)M3J_ znIVfmwa>JPaP6G4VdPvtqOIQ#2QnmHMZKlRY-#MqC0c)!NuM{&utdnL~cxA;n)%x|a2{OBak^IDlN{8!w{ zJU%=s=wsGr$yN7epe-cRU*uqhA9?NhQFy1R2Q!Z_mzSAdKMvsik5|<&_RvjZH^_&r zI_X=dlXo1^i5GXNvdW-xr%R=m8}YaJ$eQR+-z_5a?LE9n(F zLrxTKXWwzCx^g$G>%a5iJGql@ohaXw7H`zWx4Pe*uCAA+_Wt6Uyp^f#lLr|qD3s1L3bpJxRc;d^i> zmk@LNw#j>UKC{%;tD|9nI3xzYLbrGnFY;w*7*Dy>8I3pc)C76swi0{1?&1~lUgMSb zRj0g9CaI&Dn9V4VcRZTo8hDq%e(GM8r&I73r``)-R?-4>{k>G(mn_z)`|%5S4&Zy4 z#LmAk^*jOoWuvc8t0Ye#MvuT3-v?K7!o$25G(otv?g(P>TX4=x+`XJZ&Bsid=^^Tz z$(?OUp_e95<`D4fL>y;@J9-9u#f)BdEf!M08R$LVOg-b2@rji(#dGdA!X-{7=ZofC zda1jvFQYD_KCI5e?}*2GCpqR2;x62$qh9!$o?<*$=VhYywTM_FSGNuXf_F<~ZKg&u z&81FrbJqQ2-o9L>KZ!dJcoj62zQ1Mk!Si?@ZT-er{LLP8L2}dgh}AvW>^jMjr9O^* z>yw$=2PXBw@4RpjFBmv%3WmR7E@3ZPU2~o)<6WwHnEfAH!c~3ID4p2NzK44btMXr~ zRsJ@!J}=~gLwB<4pr6@Ai@1B{Qgd5-NAbwaI-TgOm=vL(n>FL#`<;q!B%)5 z{Ft8}VHe&i)xW_$66%QhTgNglnwWz>tgnny!}9%d-a0~!(baNgEmD&i-fsyU=;IUR zzGO+!I5}RxADAiHN_!ng}Z6{-+*g*ms^OTCtKyceUrQ+ zcyIA*)H!OYI!mX>cWoAP>Bo{oj!>r`98fBn${QYeO%vss0hw zoe~`BLGp<++)?-;yJEJeD`W|uWgfd?Ho;*Y;j@P0IXhq8z2tTNW2n!#qhAUDyQXla z&dL(YUH=Jqw$#VmlQ}Yl5qE0}*~Pd(rq4K!`@p+-@D8!$ex3ZiDV)nkLu7o!Ouqp> zlV14$h-@9bwGdy}DCR#*!QXHQShjN$z+yf*NW$zG#1y{0c`idpsCKZP`)Hj-GtJa9s z5VK#xzwDZ?(jev(Zlm9=NIIUBqpHn?I^lmvRX=Ue@wdx#Z1pCUo!h`1mHD#n+rzG( zGMR^RA0=>COv#byf?n_3#2$$qvR+xwT`^8p2j6=sxJa#QUPnAea~G^6{&%L!GI$}{ zok7A{#LhuBG z;O8>o;L3KujpH+$87FW5GIdzMx$iR7_BFZedmF(zIGxGlx7+*iWN%gTRpgm(9aPim zd2)^FmGjTxa^78~h98*&>KCp05N5BYfp5=vRd-jS9G`ntYvjIJxL37h`&AcSta@9# zoL5azkjwnb~z#`|c0i~8;k^37wt#9lSJU7@^qg1m0(*bj=} z>U-teL9LZ`NIq)(&aGg~bqCljJ71l5jb}d0V0kxhk@uoC@(z={pUqb19Q5o8^1zK~ zaOCt|hv%s4fI(e7b95>Y9_m9Y-qmUN<*5Ir)T8&2d;X97{D)D@z4z$UE#TdDV(nc& z`96l%{0-jb)%E20Mq)ZponNLilkL1Z)8KmU1BbeI$@DY4#<~Ia+>jrgT+1vKYMXoU zFRC3{GeFheU?`FQrH0qroX8Wy_eK&rO8&=EiDv<4OYPQzH?6WJDc@LPk zE{UGFc9rZz!*{}wKJO>Pv+zA|7sl7f=@yd5oh6s7B8R)c`48}#uLp;|?t#82frdmxSr3^T`0?4e$=tkWvysu zwhWjY<7IbAnk?0J_9+a=`rKC8V)^?&`QuqDq;Du!m9>*~B56P;(=%1mZ&mG|^a0_8 zs2`rAhPy|}d0P^CSCASL@rgY~%)MilJKc{O?;M^+^2|f&%siSWcQqczj!Nb$;P>z{ z2jX`)_QUWGGdHT^$;tTo*{5(kQ|*5dgE?T>hER3hU8Bwu@NG|URtI94^*&$SBPOZ) zBD{_d!H)-Urneta$E^cu>%pI8=~2ta#dxqc;7jXKbB{|N9gycX`pqAsSHidw-&eYv z-!uQyeQtyah9W%7$(}}wR z>e(FmmTgt%0Q}{CH+x$)sq;B7@v;heGl|*P`Fdg;ed@XDeBGzc_xS1||9%0!{}B99 z4P4aQz3O&uq`wD_a}QsG{B>$DcsLBcWK$tI`fPRIIZoY4d~e%I`6|w;b5DZ2>9Okg z5kBXMh3fcUJGm_}?wd}p(mC>CYP)Tmv3P^bW9Gsca;HDQnSJ^I9;{{f{i$a@S%ppq zm+%X?H!%`@+6wZusChO^|F2dCqGMuWIKRE8Cq#0*yI%O0?rSdF5mo;%_vIFwD)Y$4VEo zNz78Ss`PNI%4gEk1GiIFXHaR{M%nKn_I}^O9ME1_LZW23fNP@;f^&FU9)SlklDo(A znLR{4L4TBWfmOCReA92UkNFE`j9tdgl3~oQUFXz^qoF$aOQC8%N><&X!SD^#eEUYL z(Y98u=VIk@Y*kYRdEF22^#*#h`FuU`%jsQ%cb|!0CD5zp!ZGv%O;GE%d1|whm%Wsu z_D_oNCL7d#iBoNT`21sH)qaLE0axLDjr%@!A$l)-z$AEqbBpMgE>m~-e0A};c9vwz zJ8?a|uE}Z}0hS(!l;_2IW)Kng9}bZxYNA>OpGQ07^~?>H8;;WjZaW=w)v)b=>L(S* z@o)BM^dC~q^LtcNvsJY_cd549Pqnrb)qa+$TGL87p5;Byr2k@Sk{VW*sqx-9YWj@W zZeuRQf8*48DpYO1k>{2#CeH-tu5-%!#ZGePI6PRbV3}2&>-M7&n9(_ipYdQ1`AO$_ zw1X4Gld*FH}s54=jI=>22=ezJvvH5toz^AH_@(rQ}p6OH9HE_7A zl62}>D-Z6YJvj@&#s+LsIvi=>sF-W(LR??o0$*>7nR2pmy64K4%`eaq5^8 zu8wW+I`9S^*Hq%MEMpGPaQfNc!PAJrw0N2J!KwUEz>b|T?tD1%TCoK)&zw&FFmaDOK>GS0tza}&_?P+Gj-y7IkMjFCHBC;_lL{2Ib5Yz)2!iew>4;v^qc?89EWLe zG}p50Aa9#0PNlMM47|?nm#t)^tYbZ39(?Eoa!tc#S%-25d>xIahR^13FkYI`ujma8q-N6esu6-KJH zliz!56Pg!Vb4#E)zT@uy8V}RAJLUUvmAc3qyZS@b^|9nz*-!okR?bdQ+vgc-@lN?i zn`<7rS?*0>tv|ZQ4f$%hD@HB5(DJIztNErJc%O~gxLAy!SuwNLE5Olw^4icI`69{N2L{x+m^)$CE_he;#?|<0Ug6%`5=uW|1@oZL z8^^6izk`FI<~5r0nI|?x#$&r>3XYL!>T;R^4I{1|vcQE^FtV)i7kq^w3u^ilrh9?at&q_>}H9J?fBTMLw!NWBy zmEIZl1uUMg%H;(*_Q)3c2NGoOpk@n|Osnb93CY*d=kaep8LeOY=+RxgMMqz+R>{m< zdI-n~cOH=Cl~Hh-E?JT*Wue!_@?nOocN~^&QM>F;sq7c4DlB_cxfky6#tQa4nsvgs zO;zXeRoTFNp(_?JGi(TcF0koCFn<(v;#hQ(W7KzdEny}nasAX_m0nG6oj+a^@~z6+ zI8|Q8yoSe9RYt$E?VMfKzzSx&lP|PRKrfHyPM`)oO&($Q{|cjow&!Vn!Wz24`L@tXd&GDcxI}AFCXny^T$(|kw?GJ zha1!ax6m4kZ-3iHwT=c8X64GWkGMEe&wQXVwLVs%*0anB>gCVp5udNY``=FNd_)i1 zVq)*c!)o{E;ax5lXPV>FOmVXAv6ieAEQoqWFve-1lK-(oL! zKKW*Tk{p+3skUUQYNzm?&%?27KvylL7W{U%oC7^-N?NVvUkuDvp~r7;wK`r31l!<# zK8uGdB;OmkUtLGYEuCPNBT3%QLcC(UX8Ra)h5>mm?v^)*|96uoe+RF*GgKWv%mXW_ z>-NDpdFROcvx_{C?_B}sv@b;7KY8yj?4aJI?){6rJIkT2d~$)ShsbvwdgxH%c2cxD zI_=DPO6M~IW5l>shHs_cbv>T(zo?5BC8=#Iy^7Zds%^qv`nq#v zyij|^p()&)0RO&4#*ahjZ(S(kQGWln1AI@AO6Eta!D@T)aUOJi{wh8nLOo4l`%JTUkSzk+#ZFZ&X{Or;#Lsb45GsP(OB-%$9ZK5_Y%}cia1P^el7A{!CHvzCazlcocek2HFI#4?fbg z13qKSVrFNKmwC`3`e8SsIV9uPBF5jNUx2)*)Rv_39sN2s7md6zKvh#BRQ2N+9Y4=5 z$cvdr^gP(pvR>9UV)*5H;%u!<7wXHaQ}BY3Uz@mRzT!Ukq*di*#N9RYmKSB{m8L5-`x@RphOpTa{Z(vJ?OurlPk0nxz&c`=~cO{j08xv2B zw_9a~2`b;hEc|dk9q(rU#7CUhweW$LJJsY#Q}e6oYAy>@bLL<*a}GTB5JS_5t+BJ= zI1Fli%8QnUx8oD?J@U)e@$`3Z?uUP;Haa;@o-c>1c`sUDa*8~D0rGg(sKqr(t=-Hb zYwl6|H!144f2lfJI8%!b$r}c5G0Um;VR$2ltXE6_dE&WR%@di+@Co|jr+euCBfeHz z>9>VjX@hrsn0vtmw^9kW@+iKOk4LCsFS|37sNwDj*2(STRoyvSCs#2OJCj=P=bdtd zXQ;NE7z`r^?OOsaZh=oi&x_okM)0($$q(;YG4Cf!?Ki`P+}Fx3JoNwN=+>6Aa2%;R zwS!u8Dsl1_IQTO<-NWa}?ObaAF%Ir?uG%yG)E;pFP6oUQMq}SV%-nE*`Zh})#f9ov zH=n;l4wzK04i|Bo91cECk?*mrfBHDO{yT@685`xBw-r1!({Hf{Zi?LT{XBJ)ZI$;* z@`V?zCWlm4{^T;nDm6UgK?I@ImlkB>3`Jk8F3~!@q2WZ1&Nz(y#E3 zr+?FYnUj-bxhjPE3tr(_IP~LrXxKj4lIf45&%AUxHPV#nI=0S(ZcPj%gtN0FPUQuA zWN(1q;QX07<75o+G0&NLam-E~ZTI3C#fR2gsFEMJC+5wU@dva6cynV1xD6j|Ebz%R zVFTJm41MT{=mK8Zxm(JnZ6aovCG6&&t%Ge%ui*PuT)tsUYVjQl^vL-va|K7xMdr- z%nV!fImb2ZHuo--t7DGEyx+r>gaGV(e~zwLT7( zZCj_--|18UV#?Af?cIA4pHqhi8|?s)XDpYu%kO%H5KDkTiP$j4O>)OK`+#*Ox0%6 z`}D0{^@~@?`BfqmmAcXv;rLyPGwH6x?fnvewJ+cMiXOS%u&aO@WZ!ES4SN2`;!B%7~C6^ zhEIVU^)mP!d%S#e(wTLUqRtKLcs_hKW9eU7MjiYnpZQzyGMoYP{tMq&h41hu;;x)r zb2xsL8JRL(MNH-m0p~Jg_+K@>uod{g(8d$-U3SB{oJQ}f*e%RvFt8jKS&_`;c!$HE05?{wpW!S@rvv$QpKG+R0aoX^H$4x4|o(?1aBmn zBgnNw&D=o+GKMC~w3L{sEtEZMC%n;LJ2oS${8@vP*E8ZH-ifosiDHO&|;_X2eC$0X0A6>5P)ZoQefD@{|| z1qX2#IQS%U^>(k8d*g6)>?3j&&QsGo=6B5vRdd1!c{Y)+K5|wqMRU}0E?q6ZA6Cn& zY49T}QS9<;0JzPWO*rmnn?To@m{`-LU^ z9K2pT&dE7r8@l5v)m=MIwJVpaW_G?#o@ICY^YEA8R&C#4ImWM0Z40vtJ1gbL>r?&s zwQ8uM7xwE7a4MB*{*sx1`-q)S@m+pRJoFxf^PyKK&#d0t@N~c7&n)^?I+Y(K-&yKV zcb3}HiPP^6srA>r)U&J9Iz68HkbK~YEo%9WUiLNUkI^A&+ka4PNmh0=@OP#Yj}6q1 ze9+#9!K|lOsyk<$dhW?o&l3EYm$wqv8{}OcO5mtdr8ba8OIE<6qOvo=V6 zued@bZy!`iB-(dT3Opj-uOrDaT(f{(bPgF0=FuyfD$D9Ec${Noi&!ArTYj>AT`XJU zQn-^m+2;?J{YqjpbAxPCmdg4k80p^&=dp~L%)u&qWvj|7vsB(Umbj>veKi`xh?VRD zqAr|5efb?RwSmX0n`L~{A3eMX4wkzC4bph`R2c(^$x{5<9}ic_^XV#idGSAYh;e5z zxgvRQ%>=ZwBH1q|mkYuVc8Wbl*NtQ+7IVNl(^bBi{_qs;0<>vsFnQ*yXegtm$~@00 z)6hPZ{LSa_!&2gZhs=pZvc5A%wz^!|Zx}4w7v#g{wcIm_i9j7wlM7 zlk3#J+NZjLm8!R!*-sLph9w1Z`r`q)n)sVNNlgzQgx{ct;UbS(KJyTFQTT`HGtHtV zdj<@QFO+9J`ubz=GnQOpWiqoB@ROwBo%~O*n%{HFgICJ4D_NcvFz{GBoDJCc^mH}B zowz2;?^yemm=^5EtvjOvFUR6Ja%hSi`7z8+D^|lh^Qme1Sw|w| zPArfIj-YknX0T|ryq~AZ=gC1UtH3kjQ1AU2>W!fHbl4nq_w~ycv<$AZMy=~c!+$bw z^8oY7va{7Pox91tNS@r;Y8elX58-q9tCF7l^=iAA8aR)dYw#qs2jD+@n>uZOsJh`# zPusRJ-=R`xdLq<29sOXfLB5wNt< z>;FhD+q#IJ3389QcnntZIW2~tokq>JfXDl*Rbt5|*0$kG$&qm=G50&YI4vRMo$!;t zhEY#h(FZ&-JVegraz^4?9+9S!Rc`WT_?>cM=NMYYl)bWD z=Yu<8*22dIa1~4$HeVHS3sm-Vf$X31+Rbh>1bly#w!QpS_;lYOoPvyO>RiMUBzQVyV~wwq}JI6Jkv`!!z1MS z3qSHRctO(Wm7R&+eiJz6L+^giC(o1&d1_Os`=|k{v*o^K5i^ta$~Bih)qmxw(MY^K zXr^BvOO3DK*?x`KZG!XQJ$e2;PoDF{_rDjAi{>+PViDh0riLdoRQGGW9K65UMZynD zoi}&BY7NZgU2c@)Fnf=V+U2OFmi>U7oA)R9!3j-0F0dGy6yidQ5pmAMit;YPsu7s}M~IJM}vIr0?pzJBRt<{!O$qc<{- zn%B6!S}k9XQLAAa{07fmyMnlDRoi1;wU=b$51>}OIho#G_Eo;0p+6=W^+&8nzeh3e z;bZz2m-(r)kk|8V2)X7Vb!_*kqic#fa?hzFe45((bJztMhG)A%9T!p8U7v|}XcV(P zxzDbhtERueaRa!vd!9=6o7uMrUovhnXQN-nm-zn=cEP3dwHIFfBJS)wa?f#-z`&)< zq2%1(6e#1<)K)vk$#k;;u5K&5xxZ|`??*ed33r4Et3ncD7w8n}`IxOVRQ zVc~eE=)<0ZhWALE?A=z`-wToLvAME9K$@T2B-4Y$e|0)um3HQx;KB03<%~;V2E#Tq zdaF!H`N(B>E}_hy_YQOq*z%@n`FsdAxjW%CuCo^+5kIxYtuJx$obC-xVJ|q{R?$+U7u=h52Y63 zyjIOq-5&!m@+>>3mNx1;G>O)R7`2>t!FTl2=Z)Wb8F=>#v1G*i z_;?KdmIyUpLhaTuS1ngAQ0uLcYE6I_jUg5{EtUJScr{H4k_$X-wAZWgruAwJH~{7y zK_}Qjj2Y>v%T?=@^Vv_dKrLDQ@(fLp`_i*&>_fZz%d7fvhvk6Jscp$o?a>I;`qJgN zhy2nKMgB=Iy+$*Oeu(P-MJ>3N#a)oVF(>8jGhU}H+yE)8MF{)$9A~fys_#VgW zlr5N9g!HC9=hLZcmZ)pqdHDR%YJV+XEv-fL94%3^FGZf4V*c^&ct(y=b2fLx3f^m5 znp#Gtt98aswf?tQt#7xYbCSpYY(VFZ0Z$|7>z$(CFHKbc#=W!r{3F?o$F8n3(jV6| zNBVjEU~wzZC&1sJAa$InR7VrBH-93Y?bYx%FsVpK=<=yCF%h0=KZd2*q!Ep3&4>Q35PYT{>E1KVG z*(OgRUrUkwwROx#059BN4LOSWli_HHa5ocLd471!U3oHJPRu>YV{HT;NOb+DV)6Ws z;WeybCKx=+hMh9x6L(Md%kbG8`qRmqe}|v7_0wZPOu_lseuG2oTd#8b59Q&^IJ#^S zx$qQOSIm)R-)vdFA?|kbvq}!KFOQt{bbt(Q9*@A~JWBt&VY*5tEN8CSW*G-3GUFO- zz8-!lm-iCF4ie(g=G_S9jh1CLntMetdGaCZ&Tf@_u}a2^xi4m~kooUQSu@DDYv6O5 zT2%^XRaR+M`SKMi&zr7_NNUeJrm1pkF7uBDtICJg|KBWCKWx^?+n9g!{7%(=J{dn* zg6eMAjLr{C;^d|L*udE}bgZ9}#N$5I$tr zNHmEN;6s_(1pkLt+i~Z7wZq}IjtG$F?sT~gJJs}Z2=fEMyOe`!TsTRtF+9&ndgCr% z$!xFlYFlAs_DP^xcX!Lv0T=RD0N6TJ&aR27KVw%N{Fb8+o{0L`Q3;m!ZDKkdRv(ZKRmeKQy-)5~>ot7{(1Nu#`0^^<^Q1=_- z<@?G2*Rhyh*gp2f&XMQ2UTWSVH7}f}=Bg-p6i@u2b?l8)^Nj;)e(jt*Yo(Uuli+>3 z)l$e^Gaqg8mlSy)-=S{miLCgAGW?gjG^jU*G>75mD!-84yujUk6bCmy7yhn_ovG7h9qxf2Sto0Hm8?rL@gY0u2j!8n6#p-0 ze-d1I0RF_yg|eI+!>*3eD!Vmb6@v@ttHu}k(qfga#GiqlZhaVTqAgICKjZLn;1heN zM)oD4vTp|Oh8>b^CYTtVO#W6O^WEfR9}wrUUa%G8>JIIxG^L863^` zfycwRJ8aXyI_9uU^p_!qJozee;H#&|bk#1IZ{s<$smtOx=e}@s1!jTeFJgCAfUKq4 z(Ck8(8FBa@9J0KW&U@!>xdhDIiT7rOzmD#sw<(z(C({@iCQX&$=WXmABOmyK*Lh@; ztRGU-_Q0FIiMBqLI`JI5N(vqxJXt3H)$|F&L)Gnpr#dWCAh|(GhAd~u0ivmWzl~Jc zmFrYKb-v1vpkKDKllftvj@`bRow|`ao}HkouL^YHT`M#HChKI}G}XMaQMJ*7@ieX? zhl^I-G`!UNm=83k9^d~+`fb1pax>32arAyu%lrA{9=Ub!9e zbgom|npS#e;OoMuUtY)GaT)zK#W`xd;Dw2t1b;)$d8`L6h5GL6Wpce9BDY4$vjGpu zdmGtFl&dx?oQ?^-;o5Ne?q;irv)#Ck`{3(ys(%|iySY&H*WkhYV~FZb(E~Z6o?lN; z-C3XNzMYLHnt3u2scKk1M2*}(P0x+Me*;$dgBjg<)KcghKW>o^zNhN}^y>(_I^8aH zfNSjmcD3F^{kDUjee+?tH=dW9nzT6{{2jyR5KTW3wS4nS@KEoPyIvkH&+~9f&%z0> zTdtOw)p*c?l=pgf}@Iqt-9E?<@}X)z)XDFY7l$WH}5! z_eU~*$Y5D6nv8C|NVY@cRBFKc^a3;OeDDV6*P_!#(aI|E89W-K3*WuhY?>ZXJy|%ig>&8FJ>r-3*~mk-z^E z_eDq!yMqqOJ|{u8!X#OXqGVZHDD$o4!Z)C8Bu2@29XiLfdKuf{XRZSC-^JTGlsr4< zFnbi}5nLFh@`8D29XnKU>k=LN7xO9BPS^3y^*V8fL)9^OHy=8unptq}!T38~qvre4 zz??s3BK<2A?mZf>e+}L&ux3?)ntsnw^EL4M`*>_8cK#sN+Q~6bB+2zgteWo3!uL|l z?ByNk;x2qD^s{&t>eR&v>RRBFHy0c$r+*#J&Rqf*di5N+0=Lp5-cH;xtAV`E6YOFi zLYP_|#N3GjwX`p$mI|g1eu0|CmZ|ZfO?bJd(c|Bv2LA{(Oj@Ld3CxliQKb6c=x^E= zjQEw<-^!~sa4cokp#Dj-F4=M2tRcPso^{EK$7c=t=wAKh3Dj@r zqtvs_uCB;&>b!{9AB*<#fRoRXSpJ|)&Bw>9IhDI_X0+V7yq#}@~N{avQA$JVOMmZP#Ueau<5qpOF=x^x}){0_7^ye_Ala4=!A zAK0wYQ23QuXbU}SW#eA9ez;neu?N8mYN&nGQmfJA?hcY^#u1r{$urUHEN?8PcVPmW zJiot(dUFuG&->NvLtzHYhzlr&8PLdsr$%N1~2aFz;yzxcB~A8LkDpF7eST44z#+1#RJo zY*!V@_9om>w3+?`a)KMS;3+5On@7lajv6lo9q@5pPl8p((~P zTKv&{N8l@nx#K-LWupJH7w^1|ohTQbwtieT^HE8(P`sURxiy^~%gCJD{DR>NBI zvMV!G{I^rZZu)kpEetgqRni+ue#V(aPqGdfDckU~|MY9x@|MZAe!r})RC3KSng62x z+vs2qh)?D};S#=g$odr;!SQa{>$Wm0h&f%(U^IpKU;wew3%8g{>~%%S`X$f#{vp{{ z6shz^JXpKVsq~fk_@Z61b|=b`w1qy0c`}crhBL>&XD*WICi2cyG=^uV$#OgO7$`qUEy2%jKz7)2oBYlXs(aT;ORd)LhOz^4l&oeX~QZFVQn^iIwZV zY(A@TYAze0_UDf1RA{n(-?DI)-_#ps4KkO^x>&8V0xrE_mS4kbI**6$?B&dZcs~Gd zbRvBX=yUY6v|miFyJCYp3EXktbNBpvzg)YS)#N}IiKvt_Cr!@CJ~hDaHr(&j(fy-U zd~A)1Pve8!%G|yy$fLIAs^r69nPvw6e@|X7xzj`EWqmY_dM^Tvi+K1Co~)KwykNvb z>qwcClJH_2gqK)EjkZg+K=izg;NKHtWc_-nEVhxdP{UaMbxxM&gXo*%`I^JY`zmB_ zXV=b0^dqFE$-Wajx{2JcF^68jJednu%SR`e#4oZNz3H_2034 z8JG5>Ar#B7gWBunk<5x2FLMR;;?Fz4Rer{u39^rilKn=X>>sedJRnP@@Uf+PnQ;rI zmsX*zN4Lv96MuCldi%jWT3ndfCJ4wZ2_Sas<9QZ zy7?uED*JMq%1hQUYj=~5zlK+H`7)gxnx&f87qO=#Q;t#nsx8Dfefd(=_4~i`s;4$yVN2zjcQlZ?VQ(m?sQK(e(D4@eVIe9QwEkKs^QMPYS;lUNv_cxkxOk; zppN57>g4x3XYQn@tyk^gcvbeEr$!=AYB(U*`&DWjwOKA%8;(p

+Qp3mG=kxG}T@b6={{}}f}z!v(Nma3i{wyu|n(XJ8P zCH1P^8jUCVJX{O&d2hn6GGi5UHJK@6B0sajqmIp2<48ireV9#AK+;ou-6{8$@+6xzkb`WK|QrQ)wO4V zIy3R2|wq{f!aLF)bchyzALyVt}MVOx1X6Y^pjjOhq+XRYG}^{=izwIZ_v@( z539JSNJm4-r;=CdC_2W`MeCU3H$}!E&Rx-TnLh{9e$JLT1FU&Gn>gFRoXi3l-5Yq! z0~Z3|J-TJSYcKH!w~k(D8FxgMcZkCeshh3^mo`xc-Z`7O32-nw*W=q?BJ0HkvfYV} z@WLLI-nL$)w=a-Au@B8*8+@l#77OP&iRW!fk=c(kdrv>w-An?!r6pAbh4D zxpo})jve31_i$2o!DY^#OpeKpl2PnSNI1*9UHpu%rm6NP`1rZZ-aW)@2q(UN_<*{@ z+tiRdU5y{0i8rLGiCnp9t5Z#t5ppjqlskDY=g+6c&*1js(aMg2kv}ru( zC0yVj4|>*cwY`Xrd?sBjyVuhPK#g_FGBAx9X2as;+(P};zLTFv9L-pwy2t2YO^ZWM zBwrkjK48alQo*15@%#bIxpr)lEn?;jF&_77nHt@%}~lY%=9uK27fT;_z`#0`K5fMlX?j_Ex#hE>q*avvPh#t<}jL z^y~yRB;!Z;2JBws{>LBFIX_CLmyOoByQ0+haIbz3&(WD$W<7-9la9ov`6fC-dxbi0 zMpsOaRcAPwJ2gUUMxmNNBH#E8KDT#>8ve(fc>e)4Jl3j)=MvO#RgUVD_o%qWr{Z77 zv;Tt{@4?L~E}N?2%*i_XE`0f{rOZu%KX@QQrm%kI%5rZXT_EEwa;7-ym6@B^RmZPg zdzs}r4&GxD_~0+oh9Vx+iFwp#mbK)28O)S+$h2~-Oz9J-(TL6PdVC@9GcRnEtzZoI z{wT1Hywnez}VkU2O-CgVh6 zB8}(fo_G@u_I7gEpWt_1Td8AN>vep1psFsV*Sv2tJwUNKImV@$TMAWs1A7R(?Q)ET zAH3P4>W1+;Zl))%dO&3#!NvOVn#Q9ozH*qlGn2XuOndE+%y(?1M;}am7;M{dggpMzqvE&SHdqDWgCS@{Bmk%qg|y@ z=;#egRDPM0d7e|)Tf$DR8Ou~%lB<*W0%|^8tlBGzRMR&{H7VOvGc6F0W1<|l(l_G^ zQp3-qnTZFct;>^ZU>dsf0{Bbr`7AVW19$(|Xng<1ht)_d6wa07%5$oFoO4k@KK9^B zxrYp>`37pVf+RH`*d%w2pPDkq$@RlnxyXSUuaFwjDpl85BgZH7^?3VK`*V?M|GSBt zH&eA&;&mRNuWkyT<2PvGH&eHzZk1#6G&!D(ljBvN99~}Q<6hO>lShmZXOE&QL@ttJ z6@GxK9Mvrjz?(Bkbw|^R&8k9&ENBHU@`DBvf+{{|JmnMe{zfJs}HOG<@u_!5@+8MXQpHwy_3E+ z>bPRCq<9N{uow5pK%HLFKrIup8~@lkI2R*l3axCPze)<$s)SwwVB&2- zG4uM!#f~44@#Q!fmx#V_Ffn_-jIGOLx`lIhA9wzY18|T7GB0q-@}J%CjB{nny};kV zXFi1QDvNw}BHT?ddcz?2lda^H>#5f!gvuCt7*5^^=Rw^6z6JbfrCzeiyc_;x(kAu- z`O8{Kefb4-(j$1E-(0E_$(+?-@$buhy`Ou{eONA6ncSbp!pY&;y9!^?qP5iThp16f zm}jy^CwmsD>LGNrV)T$Hc)6&nN|!CA&LvMC@Q^Pjf?I*`K5!)&8~5E^C+`@J@LRQ6E2%JWlo zEPtGipAOQAy2Uy}w3M`{MWwW)G_m4*)+ zFX-J%=xry5Y=;kcW`i1*7sL7ETYf)GwdwJy{nuR8v@oCT>loF%Ak{n+5AWlrnjfoF zd+!!Gh7m92R@Ge`CP#^%9Q*LCG#pmlP-3vCTaKAge9t=exPy_uf|;{JRfmtRZf}t4 z{;$1re~Rin9}#~5pk8fvYzp<04U$rqC`bca}rNT`FCB*bRWI7kyijond7 zjG>4Qb&OJjikBLjSVT&cF*GjAvZ8{Z!r60{Wq}nb;teTLy!F%nqCdo$9T^5VoaH?4 z^S!;#JH;k*IeP70cIANl^qgXR0gF`2eldq`{%t-Gp1z+F6 z8y^mryDJ5L58ukmVWuzZ$ufAL#k~!oPrmz5O)rg=_pNkUM>6TV{4U*moI#&;AC}Jo ztA2#Xve~a|&$DmULGClCK^=?GPyTXI*Vf~YzaF8^ne2f;Be?N3dxXZ!VQxmZ+QEHo zOXznW^1i(?QC>7EZw{}$@vIpuWwm#My@$zhXoDQpMbzARIocmmhjuxx7OG-@DLMkU z?OF8nNU3sAqMYs1<@|QMDt{cT$~UJn!xi7GroUVf)Y-t* zJT-EFp7+{BI2!eqNAG-@`ixmZzARK{LlVibfC*3GAGmoI7o%1D9er`m0G*9RU(8L# z3*#C85`Jg1F)IuoIu`DMi%|^pRWm^s#Q$Cizq!9il6h=zrO~ z&lz}1o2P)E$R+GyhL(*v8B3!%JNF&ky&tEZ;1J#EW8eO!PUi5hCOeF``z>DC&<KNIC zMzCfepRCFjxu0pk_e9SuO;>d%8m>lA&o;7}>r^`p58<;EFfaQA^FlcX&&SLadfZ#@ z$+(;#Baht5WitJX`22@F^KTT$gN|i|vR>o0HLYU3eyT{mjoX>Q94-H+Hnp_D^V9P2 z%;EQ?%#G;1NB@bIVNl1VZ-$?WVlZ5j^kP zwT~ti#sCDP-XmqSY42DDun5ve8HRnWutf&P|b- zlqb_(FDpDs-j{BwY33|41L(K`IB0Z~TF$uChL#){Ie|I^!tO4PLGUu>1{EgcGn7(vrq(%uim+}~Y;kc83A%i|$$T*S9@xWKj)#1?6JLxR$E(LwnO3jL zE!5-Z-;h6y#4B^s=c4FA@T($v-t-!|`ppA}!ig5)t^WdklsN&8v{6-mE@OXttE$Mg zxxdD@UJvHH%L*ixc6_spGphMJ^x?5RDLfqWRPNxI z{;O(Y`o9<+({F#59z+Cm_x&}@3m|)uep9|joid$qY8+IiOX(9i-+;M*o6$j&y5N0d z;H=X5W7ofb((XC z@KI9zy3jj}Tpjz1=B26bM3ye4W$VgwQbQKkJ=88^Q#qJ&nT!wUbr(5bC8t%5b&)bM zO62(^J!k~4?j5qe1k=C^y?x-Xv3~d-nn+NATF#ci`JD8#WHtS^9o{uTo@vyIow~{D z;M}i0=r#D+ct_quyS#Z$d0#|pJdrPJv{U9-A9dfyHP>-%dhOi_GS*F#Q9$kI2V`{8 zoAT3TPCAb^X{SEeS7)J3I>x}|z?FB)o zpIy%Hx{kEJZSAv(iRJkBtl_g}5(aS4)$AUeNfJ5m)PiBzGu**eXcjfG4&kL^~ zmc#$vAvZlo?tVV9&Y5!GcuXFDGxJ^HVfHvx{ReF#htEF(9}b}x9D@g5-i5c^fi?mU z3_>3nIGx%{mb(N@>4=vr>!>RKSS;tG$ErkkaGg!j*@0a;_wGipTB>Sa3sc>%cj(fi zdU{2FSr^G7P9pc3!d$vf3e>wIT90?9#RR>4EGD?%i6Y!ZrbWwWobhOTJW;eCf$Z`Il9DxK? ztVNr0IaG1FUKM3{_&;)yN5ID~E@TaYmyS+RRU`LmwyHV<-`u`IHL15%+gqjacvHXDgS%l7M=NYP6C=tzm-RmVrigxbv_E@us%5;#Gd+{1*hxRIO-4Nob|Rm Date: Wed, 19 Feb 2025 17:49:07 -0800 Subject: [PATCH 06/10] All (except one) tests passing on python 3.12 --- firefly_client/fc_utils.py | 10 +++++--- firefly_client/firefly_client.py | 12 +++++---- pyproject.toml | 1 + test/conftest.py | 3 +-- test/container.py | 1 - test/test_5_instances_3_channels.py | 8 +++--- test/test_basic_demo_tableload.py | 10 ++++---- test/test_demo_3color.py | 24 ++++-------------- test/test_demo_HiPS.py | 10 +++++--- test/test_demo_advanced_all.py | 10 +++++--- test/test_demo_advanced_steps.py | 9 ++++--- test/test_demo_advanced_table_images.py | 12 +++++---- ...test_demo_advanced_tables_images_upload.py | 13 ++++++---- test/test_demo_basic.py | 10 +++++--- test/test_demo_lsst_footprint.py | 10 +++++--- test/test_demo_region.py | 6 +++-- test/test_demo_show-image.py | 10 +++++--- test/test_plot_interface.py | 6 +++-- test/test_simple_callback.py | 6 +++-- test/test_simple_callback_response.py | 10 +++++--- test/test_socket_not_added_until_listener.py | 12 ++++++--- uv.lock | 14 ++++++++++ wise-00.fits | Bin 216000 -> 0 bytes 23 files changed, 121 insertions(+), 86 deletions(-) delete mode 100644 wise-00.fits diff --git a/firefly_client/fc_utils.py b/firefly_client/fc_utils.py index 3aa3fe2..085818d 100644 --- a/firefly_client/fc_utils.py +++ b/firefly_client/fc_utils.py @@ -2,8 +2,6 @@ import json import mimetypes import urllib.parse -from collections.abc import Iterable -from typing import Sequence class DebugMarker: @@ -82,9 +80,13 @@ def is_url(url): return image_source -def ensure3[T](val: Sequence[T] | T, name: str) -> list[T]: +def ensure3[T](val: list[T] | set[T] | tuple[T] | T, name: str) -> list[T]: """Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError""" - ret: list[T] = list(val) if isinstance(val, Sequence) else [val, val, val] + ret: list[T] = ( + list(val) + if isinstance(val, list) or isinstance(val, set) or isinstance(val, tuple) + else [val, val, val] + ) if not len(ret) == 3: raise ValueError("%s list should have 3 items" % name) return ret diff --git a/firefly_client/firefly_client.py b/firefly_client/firefly_client.py index c86fe48..ab1466e 100644 --- a/firefly_client/firefly_client.py +++ b/firefly_client/firefly_client.py @@ -533,7 +533,9 @@ def display_url(self, url=None): else: print("Open your web browser to {}".format(url)) - def launch_browser(self, channel=None, force=False, verbose=True): + def launch_browser( + self, channel=None, force=False, verbose=True + ) -> tuple[bool, str]: """ Launch a browser with the Firefly Tools viewer and the channel set. @@ -1008,14 +1010,14 @@ def show_table( Usage of the table search, such as *'catalog_overlay'*. **position** : `str` Target position, such as *'10.68479;41.26906;EQ_J2000'*. - **SearchMethod** : {'Cone', 'Eliptical', 'Box', 'Polygon', 'Table', 'AllSky'} + **SearchMethod** : {'Cone', 'Elliptical', 'Box', 'Polygon', 'Table', 'AllSky'} Target search method. **radius** : `float` - The radius for *'Cone'* or the semi-major axis for *'Eliptical'* search in terms of unit *arcsec*. + The radius for *'Cone'* or the semi-major axis for *'Elliptical'* search in terms of unit *arcsec*. **posang** : `float` - Position angle for *'Eliptical'* search in terms of unit *arcsec*. + Position angle for *'Elliptical'* search in terms of unit *arcsec*. **ratio** : `float` - Axial ratio for *'Eliptical'* search. + Axial ratio for *'Elliptical'* search. **size** : `float` Side size for *'Box'* search in terms of unit *arcsec*. **polygon** : `str` diff --git a/pyproject.toml b/pyproject.toml index c565708..68c4a9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -237,6 +237,7 @@ test = [ "pytest-container>=0.4.3", "pytest-cov>=6.0.0", "pytest-doctestplus>=1.4.0", + "pytest-mock>=3.14.0", "pytest-xdist>=3.6.1", "tox>=4.24.1", ] diff --git a/test/conftest.py b/test/conftest.py index 34d28e1..4af366a 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,5 +1,4 @@ -from pytest_container import (add_logging_level_options, - set_logging_level_from_cli_args) +from pytest_container import add_logging_level_options, set_logging_level_from_cli_args def pytest_addoption(parser): diff --git a/test/container.py b/test/container.py index b0d5ca9..82a4f31 100644 --- a/test/container.py +++ b/test/container.py @@ -7,7 +7,6 @@ url="docker.io/ipac/firefly:latest", extra_launch_args=["--memory=4g"], entry_point=EntrypointSelection.AUTO, - singleton=True, forwarded_ports=[ PortForwarding( container_port=8080, diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py index 255fb65..51b05d5 100644 --- a/test/test_5_instances_3_channels.py +++ b/test/test_5_instances_3_channels.py @@ -1,20 +1,22 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_5_instances_3_channels(container: ContainerData): +def test_5_instances_3_channels(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" time.sleep(5) + mocker.patch("webbrowser.open") def listener1(ev): print("l1") diff --git a/test/test_basic_demo_tableload.py b/test/test_basic_demo_tableload.py index 40a68c3..5296cd8 100644 --- a/test/test_basic_demo_tableload.py +++ b/test/test_basic_demo_tableload.py @@ -1,22 +1,22 @@ import os import time from pathlib import Path -from test.container import FIREFLY_CONTAINER from urllib import request import pytest -from pytest_container.container import ContainerData - from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_basic_tableload(container: ContainerData): +def test_basic_tableload(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_3color.py b/test/test_demo_3color.py index f4c99a0..ad7e68b 100644 --- a/test/test_demo_3color.py +++ b/test/test_demo_3color.py @@ -1,19 +1,20 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_3color(container: ContainerData): +def test_3color(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" @@ -59,28 +60,13 @@ def test_3color(container: ContainerData): ] fc.show_fits_3color(threeC, plot_id="wise_m101", viewer_id=viewer_id) - - # In[3]: - # Set stretch using hue-preserving algorithm with scaling coefficients .15 for RED, 1.0 for GREEN, and .4 for BLUE. fc.set_stretch_hprgb( plot_id="wise_m101", asinh_q_value=4.2, scaling_k=[0.15, 1, 0.4] ) - - # In[4]: - # Set per-band stretch fc.set_stretch(plot_id="wise_m101", stype="ztype", algorithm="asinh", band="ALL") - - # In[5]: - # Set contrast and bias for each band fc.set_rgb_colors(plot_id="wise_m101", bias=[0.4, 0.6, 0.5], contrast=[1.5, 1, 2]) - - # In[6]: - # Set to use red and blue band only, with default bias and contrast fc.set_rgb_colors(plot_id="wise_m101", use_green=False) - - -# In[ ]: diff --git a/test/test_demo_HiPS.py b/test/test_demo_HiPS.py index 7abf88e..b7bc2dd 100644 --- a/test/test_demo_HiPS.py +++ b/test/test_demo_HiPS.py @@ -1,14 +1,14 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData - from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_hi_ps(container: ContainerData): +def test_hi_ps(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 @@ -16,6 +16,8 @@ def test_hi_ps(container: ContainerData): time.sleep(5) + mocker.patch("webbrowser.open") + host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" fc = FireflyClient.make_client(url=host, launch_browser=False) diff --git a/test/test_demo_advanced_all.py b/test/test_demo_advanced_all.py index 5249add..84a697f 100644 --- a/test/test_demo_advanced_all.py +++ b/test/test_demo_advanced_all.py @@ -1,19 +1,21 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData - from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_adv(container: ContainerData): +def test_adv(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") + time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_advanced_steps.py b/test/test_demo_advanced_steps.py index 9a6206b..81c6335 100644 --- a/test/test_demo_advanced_steps.py +++ b/test/test_demo_advanced_steps.py @@ -1,20 +1,23 @@ import time -from test.container import FIREFLY_CONTAINER import pytest from astropy.utils.data import download_file -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_adv_steps(container: ContainerData): +def test_adv_steps(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_advanced_table_images.py b/test/test_demo_advanced_table_images.py index 27d6b8d..dbc9740 100644 --- a/test/test_demo_advanced_table_images.py +++ b/test/test_demo_advanced_table_images.py @@ -1,20 +1,22 @@ import time -from test import firefly_slate_demo as fs -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_adv_table_imgs(container: ContainerData): +def test_adv_table_imgs(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_advanced_tables_images_upload.py b/test/test_demo_advanced_tables_images_upload.py index 57953eb..40c5eec 100644 --- a/test/test_demo_advanced_tables_images_upload.py +++ b/test/test_demo_advanced_tables_images_upload.py @@ -1,15 +1,16 @@ import time -from test import firefly_slate_demo as fs -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData +from pytest_mock import MockerFixture +from test import firefly_slate_demo as fs +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_adv_table_imgs_upload(container: ContainerData): +def test_adv_table_imgs_upload(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 @@ -19,7 +20,9 @@ def test_adv_table_imgs_upload(container: ContainerData): host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" + mocker.patch("webbrowser.open") fc = FireflyClient.make_client(url=host, launch_browser=False) + # ## Display tables, images, XY charts, and Histograms in Window/Grid like layout # # Each rendered unit on Firefly. @@ -35,7 +38,7 @@ def test_adv_table_imgs_upload(container: ContainerData): fs.add_simple_image_table(fc) # add table in cell 'main' fs.add_simple_m31_image_table(fc) - f = "/Users/roby/fits/2mass-m31-2412rows.tbl" + f = "/Users/roby/fits/2mass-m31-2412rows.tbl" # FIX: This needs to be either a URL download or a file included in the repo file = fc.upload_file(f) fc.show_table(file) fc.show_table("${cache-dir}/upload_2482826742890803252.fits") diff --git a/test/test_demo_basic.py b/test/test_demo_basic.py index cf4e71a..09841a6 100644 --- a/test/test_demo_basic.py +++ b/test/test_demo_basic.py @@ -1,20 +1,22 @@ import time -from test.container import FIREFLY_CONTAINER import pytest from astropy.utils.data import download_file -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_basics(container: ContainerData): +def test_basics(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_lsst_footprint.py b/test/test_demo_lsst_footprint.py index a5ba2fd..24cd7d5 100644 --- a/test/test_demo_lsst_footprint.py +++ b/test/test_demo_lsst_footprint.py @@ -1,20 +1,22 @@ import time -from test.container import FIREFLY_CONTAINER import pytest from astropy.utils.data import download_file -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_lsst_footprint(container: ContainerData): +def test_lsst_footprint(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_region.py b/test/test_demo_region.py index 25400c9..1da1b5f 100644 --- a/test/test_demo_region.py +++ b/test/test_demo_region.py @@ -1,4 +1,6 @@ import time + +from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER import pytest @@ -9,12 +11,12 @@ @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_region(container: ContainerData): +def test_region(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_demo_show-image.py b/test/test_demo_show-image.py index 2995d8c..8a5c305 100644 --- a/test/test_demo_show-image.py +++ b/test/test_demo_show-image.py @@ -1,19 +1,21 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_show_img(container: ContainerData): +def test_show_img(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_plot_interface.py b/test/test_plot_interface.py index f7b28f3..5af9d22 100644 --- a/test/test_plot_interface.py +++ b/test/test_plot_interface.py @@ -1,4 +1,6 @@ import time + +from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER import pytest @@ -12,12 +14,12 @@ @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_plt(container: ContainerData): +def test_plt(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_simple_callback.py b/test/test_simple_callback.py index d8a5601..6b0e038 100644 --- a/test/test_simple_callback.py +++ b/test/test_simple_callback.py @@ -1,4 +1,6 @@ import time + +from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER import pytest @@ -8,12 +10,12 @@ @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_simple_callback(container: ContainerData): +def test_simple_callback(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/test/test_simple_callback_response.py b/test/test_simple_callback_response.py index ed12da9..62fab0b 100644 --- a/test/test_simple_callback_response.py +++ b/test/test_simple_callback_response.py @@ -1,19 +1,21 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_simple_callback_response(container: ContainerData): +def test_simple_callback_response(container: ContainerData, mocker: MockerFixture): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) FireflyClient._debug = False diff --git a/test/test_socket_not_added_until_listener.py b/test/test_socket_not_added_until_listener.py index 2a8f249..1c14bb3 100644 --- a/test/test_socket_not_added_until_listener.py +++ b/test/test_socket_not_added_until_listener.py @@ -1,19 +1,23 @@ import time -from test.container import FIREFLY_CONTAINER import pytest -from pytest_container.container import ContainerData from firefly_client import FireflyClient +from pytest_container.container import ContainerData + +from pytest_mock import MockerFixture +from test.container import FIREFLY_CONTAINER @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) -def test_socket_not_added_until_listener(container: ContainerData): +def test_socket_not_added_until_listener( + container: ContainerData, mocker: MockerFixture +): assert container.forwarded_ports[0].host_port >= 8000 assert container.forwarded_ports[0].host_port <= 65534 assert container.forwarded_ports[0].container_port == 8080 assert container.forwarded_ports[0].bind_ip == "127.0.0.1" - + mocker.patch("webbrowser.open") time.sleep(5) host = f"http://{container.forwarded_ports[0].bind_ip}:{container.forwarded_ports[0].host_port}/firefly" diff --git a/uv.lock b/uv.lock index 2058e34..d0683e2 100644 --- a/uv.lock +++ b/uv.lock @@ -636,6 +636,7 @@ test = [ { name = "pytest-container" }, { name = "pytest-cov" }, { name = "pytest-doctestplus" }, + { name = "pytest-mock" }, { name = "pytest-xdist" }, { name = "tox" }, ] @@ -670,6 +671,7 @@ test = [ { name = "pytest-container", specifier = ">=0.4.3" }, { name = "pytest-cov", specifier = ">=6.0.0" }, { name = "pytest-doctestplus", specifier = ">=1.4.0" }, + { name = "pytest-mock", specifier = ">=3.14.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" }, { name = "tox", specifier = ">=4.24.1" }, ] @@ -1457,6 +1459,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/08/0e0e38a6046f91ad6ae352c0639c1b6dd90e2cd53ab2d2282d1d231535fb/pytest_doctestplus-1.4.0-py3-none-any.whl", hash = "sha256:cfbae130ec90d4a2831819bbbfd097121b8e55f1e4d20a47ea992e4eaad2539a", size = 25236 }, ] +[[package]] +name = "pytest-mock" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, +] + [[package]] name = "pytest-testinfra" version = "10.1.1" diff --git a/wise-00.fits b/wise-00.fits deleted file mode 100644 index ffd918ae47cfcfea6eaa9035f9b57f2740daaf0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 216000 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

bRwC)Ric?NXG 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 Date: Wed, 19 Feb 2025 18:15:50 -0800 Subject: [PATCH 07/10] All (except one) tests passing on python 3.10-3.14 --- .gitignore | 1 + firefly_client/fc_utils.py | 7 +- pyproject.toml | 64 ++- uv.lock | 1078 ------------------------------------ 4 files changed, 42 insertions(+), 1108 deletions(-) diff --git a/.gitignore b/.gitignore index fc41eb8..f8cd4a6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ *.py[cod] *$py.class +*.lock # C extensions *.so diff --git a/firefly_client/fc_utils.py b/firefly_client/fc_utils.py index 085818d..de065d7 100644 --- a/firefly_client/fc_utils.py +++ b/firefly_client/fc_utils.py @@ -2,6 +2,7 @@ import json import mimetypes import urllib.parse +from typing import TypeVar class DebugMarker: @@ -80,7 +81,11 @@ def is_url(url): return image_source -def ensure3[T](val: list[T] | set[T] | tuple[T] | T, name: str) -> list[T]: +# Generic type for ensure3 +T = TypeVar("T") + + +def ensure3(val: list[T] | set[T] | tuple[T] | T, name: str) -> list[T]: """Make sure that the value is a scalar or a list with 3 values otherwise raise ValueError""" ret: list[T] = ( list(val) diff --git a/pyproject.toml b/pyproject.toml index 68c4a9c..5a60c24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,16 +38,19 @@ Homepage = "https://github.com/Caltech-IPAC/firefly_client" Documentation = "https://caltech-ipac.github.io/firefly_client" Repository = "http://github.com/Caltech-IPAC/firefly_client.git" -[project.optional-dependencies] +[dependency-groups] docs = [ - "Sphinx~=7.1.0", - "sphinx-automodapi", - "pydata-sphinx-theme", - "myst-parser", + "myst-parser>=4.0.1", + "pydata-sphinx-theme>=0.16.1", + "sphinx~=7.1.0", + "sphinx-automodapi>=0.18.0", ] test = [ "pytest>=8.3.4", "pytest-container>=0.4.3", + "pytest-cov>=6.0.0", + "pytest-doctestplus>=1.4.0", + "pytest-mock>=3.14.0", "pytest-xdist>=3.6.1", "tox>=4.24.1", ] @@ -56,7 +59,17 @@ test = [ testpaths = ["firefly_client", "docs", "test"] doctest_plus = "enabled" text_file_format = "rst" -addopts = ["--doctest-rst", "-n 4", "--import-mode=importlib"] +addopts = [ + "--doctest-rst", + "-n 4", + "--import-mode=importlib", + "--cov=firefly_client", + "--cov-append", + "--cov-report=term-missing", + "--cov-report=xml:coverage.xml", + "--cov-report=html:coverage", + "--doctest-modules", +] [tool.coverage.run] omit = [ @@ -96,21 +109,26 @@ exclude_lines = [ ] [tool.tox] -env_list = ["3.10", "3.11", "3.12", "3.13", "3.14", "build_docs"] +env_list = ["clean", "3.10", "3.11", "3.12", "3.13", "3.14", "build_docs"] requires = ["tox>=4.0", "tox-uv>=1.20"] [tool.tox.env_run_base] -runner = "uv-venv-lock-runner" commands = [ [ "pytest", - "--cov=firefly-client", + "--doctest-rst", + "-n 4", + "--import-mode=importlib", + "--cov=firefly_client", + "--cov-append", "--cov-report=term-missing", "--cov-report=xml:coverage.xml", + "--cov-report=html:coverage", "--doctest-modules", "{posargs}", ], ] +runner = "uv-venv-lock-runner" description = "run tests with the oldest supported version of key dependencies on {base_python}" pass_env = ["TOXENV", "CI", "CC", "LOCALE_ARCHIVE", "LC_ALL"] set_env = { MPLBACKEND = "agg" } @@ -118,6 +136,14 @@ dependency_groups = ["test"] uv_python_preference = "only-managed" allowlist_externals = ["python"] +[tool.tox.env.clean] +description = "Clean artifacts before testing" +commands = [["coverage", "erase"]] +runner = "uv-venv-lock-runner" +dependency_groups = ["test"] +uv_python_preference = "only-managed" +allowlist_externals = ["python"] + [tool.tox.env.build_docs] description = "invoke sphinx-build to build the HTML docs" change_dir = "docs" @@ -142,7 +168,6 @@ commands = [ uv_python_preference = "only-managed" allowlist_externals = ["python"] - [tool.ruff] # Exclude a variety of commonly ignored directories. exclude = [ @@ -223,25 +248,6 @@ docstring-code-format = true # enabled. docstring-code-line-length = "dynamic" -[dependency-groups] -docs = [ - "myst-parser>=4.0.1", - "pydata-sphinx-theme>=0.16.1", - "sphinx~=7.1.0", - "sphinx-automodapi>=0.18.0", -] -test = [ - "ipykernel>=6.29.5", - "jupyter-firefly-extensions>=4.3.1", - "pytest>=8.3.4", - "pytest-container>=0.4.3", - "pytest-cov>=6.0.0", - "pytest-doctestplus>=1.4.0", - "pytest-mock>=3.14.0", - "pytest-xdist>=3.6.1", - "tox>=4.24.1", -] - [tool.ruff.lint.isort] case-sensitive = true combine-as-imports = true diff --git a/uv.lock b/uv.lock index d0683e2..bedc0a6 100644 --- a/uv.lock +++ b/uv.lock @@ -27,76 +27,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/34/d4e1c02d3bee589efb5dfa17f88ea08bdb3e3eac12bc475462aec52ed223/alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92", size = 13511 }, ] -[[package]] -name = "anyio" -version = "4.8.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/73/199a98fc2dae33535d6b8e8e6ec01f8c1d76c9adb096c6b7d64823038cde/anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a", size = 181126 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/eb/e7f063ad1fec6b3178a3cd82d1a3c4de82cccf283fc42746168188e1cdd5/anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a", size = 96041 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, -] - -[[package]] -name = "argon2-cffi" -version = "23.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "argon2-cffi-bindings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124 }, -] - -[[package]] -name = "argon2-cffi-bindings" -version = "21.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658 }, - { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583 }, - { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168 }, - { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709 }, - { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613 }, - { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583 }, - { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475 }, - { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698 }, - { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817 }, - { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104 }, -] - -[[package]] -name = "arrow" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, - { name = "types-python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2e/00/0f6e8fcdb23ea632c866620cc872729ff43ed91d284c866b515c6342b173/arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85", size = 131960 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/ed/e97229a566617f2ae958a6b13e7cc0f585470eac730a73e9e82c32a3cdd2/arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80", size = 66419 }, -] - [[package]] name = "astropy" version = "6.1.7" @@ -191,24 +121,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/e2/496d0136d7b1446ad2cfcd4aacae8220301d9789400a916da8d79019c487/astropy_iers_data-0.2025.2.17.0.34.13-py3-none-any.whl", hash = "sha256:2024e3251e1360cdbd978ccf19152610396d2324f5b2af19746e903e243b7566", size = 1945936 }, ] -[[package]] -name = "asttokens" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 }, -] - -[[package]] -name = "attrs" -version = "25.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/7c/fdf464bcc51d23881d110abd74b512a42b3d5d376a55a831b44c603ae17f/attrs-25.1.0.tar.gz", hash = "sha256:1c97078a80c814273a76b2a298a932eb681c87415c11dee0a6921de7f1b02c3e", size = 810562 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/30/d4986a882011f9df997a55e6becd864812ccfcd821d64aac8570ee39f719/attrs-25.1.0-py3-none-any.whl", hash = "sha256:c75a69e28a550a7e93789579c22aa26b0f5b83b75dc4e08fe092980051e1090a", size = 63152 }, -] - [[package]] name = "babel" version = "2.17.0" @@ -231,23 +143,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/49/6abb616eb3cbab6a7cca303dc02fdf3836de2e0b834bf966a7f5271a34d8/beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16", size = 186015 }, ] -[[package]] -name = "bleach" -version = "6.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "webencodings" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406 }, -] - -[package.optional-dependencies] -css = [ - { name = "tinycss2" }, -] - [[package]] name = "cachetools" version = "5.5.1" @@ -266,63 +161,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, ] -[[package]] -name = "cffi" -version = "1.17.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pycparser" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, - { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, - { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, - { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, - { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, - { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, - { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, - { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, - { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, - { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, - { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, - { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, - { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, - { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, - { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, - { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, - { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, - { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, - { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, - { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, - { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, - { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, - { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, - { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, - { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, - { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, - { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, - { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, - { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, - { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, - { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, - { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, - { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, - { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, - { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, - { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, - { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, - { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, - { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, - { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, - { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, - { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, - { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, - { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, - { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, -] - [[package]] name = "chardet" version = "5.2.0" @@ -402,18 +240,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] -[[package]] -name = "comm" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, -] - [[package]] name = "coverage" version = "7.6.12" @@ -479,49 +305,6 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] -[[package]] -name = "debugpy" -version = "1.8.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/68/25/c74e337134edf55c4dfc9af579eccb45af2393c40960e2795a94351e8140/debugpy-1.8.12.tar.gz", hash = "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce", size = 1641122 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/19/dd58334c0a1ec07babf80bf29fb8daf1a7ca4c1a3bbe61548e40616ac087/debugpy-1.8.12-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a", size = 2076091 }, - { url = "https://files.pythonhosted.org/packages/4c/37/bde1737da15f9617d11ab7b8d5267165f1b7dae116b2585a6643e89e1fa2/debugpy-1.8.12-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45", size = 3560717 }, - { url = "https://files.pythonhosted.org/packages/d9/ca/bc67f5a36a7de072908bc9e1156c0f0b272a9a2224cf21540ab1ffd71a1f/debugpy-1.8.12-cp310-cp310-win32.whl", hash = "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c", size = 5180672 }, - { url = "https://files.pythonhosted.org/packages/c1/b9/e899c0a80dfa674dbc992f36f2b1453cd1ee879143cdb455bc04fce999da/debugpy-1.8.12-cp310-cp310-win_amd64.whl", hash = "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9", size = 5212702 }, - { url = "https://files.pythonhosted.org/packages/af/9f/5b8af282253615296264d4ef62d14a8686f0dcdebb31a669374e22fff0a4/debugpy-1.8.12-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5", size = 2174643 }, - { url = "https://files.pythonhosted.org/packages/ef/31/f9274dcd3b0f9f7d1e60373c3fa4696a585c55acb30729d313bb9d3bcbd1/debugpy-1.8.12-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7", size = 3133457 }, - { url = "https://files.pythonhosted.org/packages/ab/ca/6ee59e9892e424477e0c76e3798046f1fd1288040b927319c7a7b0baa484/debugpy-1.8.12-cp311-cp311-win32.whl", hash = "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb", size = 5106220 }, - { url = "https://files.pythonhosted.org/packages/d5/1a/8ab508ab05ede8a4eae3b139bbc06ea3ca6234f9e8c02713a044f253be5e/debugpy-1.8.12-cp311-cp311-win_amd64.whl", hash = "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1", size = 5130481 }, - { url = "https://files.pythonhosted.org/packages/ba/e6/0f876ecfe5831ebe4762b19214364753c8bc2b357d28c5d739a1e88325c7/debugpy-1.8.12-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498", size = 2500846 }, - { url = "https://files.pythonhosted.org/packages/19/64/33f41653a701f3cd2cbff8b41ebaad59885b3428b5afd0d93d16012ecf17/debugpy-1.8.12-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06", size = 4222181 }, - { url = "https://files.pythonhosted.org/packages/32/a6/02646cfe50bfacc9b71321c47dc19a46e35f4e0aceea227b6d205e900e34/debugpy-1.8.12-cp312-cp312-win32.whl", hash = "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d", size = 5227017 }, - { url = "https://files.pythonhosted.org/packages/da/a6/10056431b5c47103474312cf4a2ec1001f73e0b63b1216706d5fef2531eb/debugpy-1.8.12-cp312-cp312-win_amd64.whl", hash = "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969", size = 5267555 }, - { url = "https://files.pythonhosted.org/packages/cf/4d/7c3896619a8791effd5d8c31f0834471fc8f8fb3047ec4f5fc69dd1393dd/debugpy-1.8.12-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f", size = 2485246 }, - { url = "https://files.pythonhosted.org/packages/99/46/bc6dcfd7eb8cc969a5716d858e32485eb40c72c6a8dc88d1e3a4d5e95813/debugpy-1.8.12-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9", size = 4218616 }, - { url = "https://files.pythonhosted.org/packages/03/dd/d7fcdf0381a9b8094da1f6a1c9f19fed493a4f8576a2682349b3a8b20ec7/debugpy-1.8.12-cp313-cp313-win32.whl", hash = "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180", size = 5226540 }, - { url = "https://files.pythonhosted.org/packages/25/bd/ecb98f5b5fc7ea0bfbb3c355bc1dd57c198a28780beadd1e19915bf7b4d9/debugpy-1.8.12-cp313-cp313-win_amd64.whl", hash = "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c", size = 5267134 }, - { url = "https://files.pythonhosted.org/packages/38/c4/5120ad36405c3008f451f94b8f92ef1805b1e516f6ff870f331ccb3c4cc0/debugpy-1.8.12-py2.py3-none-any.whl", hash = "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6", size = 5229490 }, -] - -[[package]] -name = "decorator" -version = "5.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604 }, -] - [[package]] name = "deprecation" version = "2.1.0" @@ -570,24 +353,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612 }, ] -[[package]] -name = "executing" -version = "2.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702 }, -] - -[[package]] -name = "fastjsonschema" -version = "2.21.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924 }, -] - [[package]] name = "filelock" version = "3.17.0" @@ -608,20 +373,6 @@ dependencies = [ { name = "websocket-client" }, ] -[package.optional-dependencies] -docs = [ - { name = "myst-parser" }, - { name = "pydata-sphinx-theme" }, - { name = "sphinx" }, - { name = "sphinx-automodapi" }, -] -test = [ - { name = "pytest" }, - { name = "pytest-container" }, - { name = "pytest-xdist" }, - { name = "tox" }, -] - [package.dev-dependencies] docs = [ { name = "myst-parser" }, @@ -630,8 +381,6 @@ docs = [ { name = "sphinx-automodapi" }, ] test = [ - { name = "ipykernel" }, - { name = "jupyter-firefly-extensions" }, { name = "pytest" }, { name = "pytest-container" }, { name = "pytest-cov" }, @@ -644,18 +393,9 @@ test = [ [package.metadata] requires-dist = [ { name = "astropy", specifier = ">=6" }, - { name = "myst-parser", marker = "extra == 'docs'" }, - { name = "pydata-sphinx-theme", marker = "extra == 'docs'" }, - { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.4" }, - { name = "pytest-container", marker = "extra == 'test'", specifier = ">=0.4.3" }, - { name = "pytest-xdist", marker = "extra == 'test'", specifier = ">=3.6.1" }, { name = "requests" }, - { name = "sphinx", marker = "extra == 'docs'", specifier = "~=7.1.0" }, - { name = "sphinx-automodapi", marker = "extra == 'docs'" }, - { name = "tox", marker = "extra == 'test'", specifier = ">=4.24.1" }, { name = "websocket-client" }, ] -provides-extras = ["docs", "test"] [package.metadata.requires-dev] docs = [ @@ -665,8 +405,6 @@ docs = [ { name = "sphinx-automodapi", specifier = ">=0.18.0" }, ] test = [ - { name = "ipykernel", specifier = ">=6.29.5" }, - { name = "jupyter-firefly-extensions", specifier = ">=4.3.1" }, { name = "pytest", specifier = ">=8.3.4" }, { name = "pytest-container", specifier = ">=0.4.3" }, { name = "pytest-cov", specifier = ">=6.0.0" }, @@ -676,15 +414,6 @@ test = [ { name = "tox", specifier = ">=4.24.1" }, ] -[[package]] -name = "fqdn" -version = "1.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 }, -] - [[package]] name = "idna" version = "3.10" @@ -712,76 +441,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] -[[package]] -name = "ipykernel" -version = "6.29.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "appnope", marker = "sys_platform == 'darwin'" }, - { name = "comm" }, - { name = "debugpy" }, - { name = "ipython" }, - { 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/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, -] - -[[package]] -name = "ipython" -version = "8.32.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "decorator" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "jedi" }, - { name = "matplotlib-inline" }, - { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, - { name = "prompt-toolkit" }, - { name = "pygments" }, - { name = "stack-data" }, - { name = "traitlets" }, - { name = "typing-extensions", marker = "python_full_version < '3.12'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/80/4d2a072e0db7d250f134bc11676517299264ebe16d62a8619d49a78ced73/ipython-8.32.0.tar.gz", hash = "sha256:be2c91895b0b9ea7ba49d33b23e2040c352b33eb6a519cca7ce6e0c743444251", size = 5507441 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/e1/f4474a7ecdb7745a820f6f6039dc43c66add40f1bcc66485607d93571af6/ipython-8.32.0-py3-none-any.whl", hash = "sha256:cae85b0c61eff1fc48b0a8002de5958b6528fa9c8defb1894da63f42613708aa", size = 825524 }, -] - -[[package]] -name = "isoduration" -version = "20.11.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "arrow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278 }, -] - [[package]] name = "jinja2" version = "3.1.5" @@ -794,168 +453,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bd/0f/2ba5fbcd631e3e88689309dbe978c5769e883e4b84ebfe7da30b43275c5a/jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb", size = 134596 }, ] -[[package]] -name = "jsonpointer" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595 }, -] - -[[package]] -name = "jsonschema" -version = "4.23.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/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, -] - -[package.optional-dependencies] -format-nongpl = [ - { name = "fqdn" }, - { name = "idna" }, - { name = "isoduration" }, - { name = "jsonpointer" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "uri-template" }, - { name = "webcolors" }, -] - -[[package]] -name = "jsonschema-specifications" -version = "2024.10.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, -] - -[[package]] -name = "jupyter-client" -version = "8.6.3" -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/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, -] - -[[package]] -name = "jupyter-core" -version = "5.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, - { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, -] - -[[package]] -name = "jupyter-events" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonschema", extra = ["format-nongpl"] }, - { name = "packaging" }, - { name = "python-json-logger" }, - { name = "pyyaml" }, - { name = "referencing" }, - { name = "rfc3339-validator" }, - { name = "rfc3986-validator" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430 }, -] - -[[package]] -name = "jupyter-firefly-extensions" -version = "4.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "firefly-client" }, - { name = "jupyter-server" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/68/2c80a7aec7ba0ceb9d004890a6291b20b93a1cdc7a913a527b6235a7d94b/jupyter_firefly_extensions-4.3.1.tar.gz", hash = "sha256:f40d311a415cdb79edae6f1e20f7a3bfd80b2b5200ea7173890b145267e34285", size = 833747 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/0a/5d8de1db9f4543f463a2e8a0eab3fbb149350258474480d7f57f1c6efcf0/jupyter_firefly_extensions-4.3.1-py3-none-any.whl", hash = "sha256:7373e5cbeebffd18c6ec2c599a37a2ee7b647149889fab5fa099189b90cc0170", size = 1643834 }, -] - -[[package]] -name = "jupyter-server" -version = "2.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "argon2-cffi" }, - { name = "jinja2" }, - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "jupyter-events" }, - { name = "jupyter-server-terminals" }, - { name = "nbconvert" }, - { name = "nbformat" }, - { name = "overrides" }, - { name = "packaging" }, - { name = "prometheus-client" }, - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "pyzmq" }, - { name = "send2trash" }, - { name = "terminado" }, - { name = "tornado" }, - { name = "traitlets" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/61/8c/df09d4ab646141f130f9977b32b206ba8615d1969b2eba6a2e84b7f89137/jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084", size = 725227 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/a2/89eeaf0bb954a123a909859fa507fa86f96eb61b62dc30667b60dbd5fdaf/jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3", size = 385826 }, -] - -[[package]] -name = "jupyter-server-terminals" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "terminado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884 }, -] - [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1026,18 +523,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, ] -[[package]] -name = "matplotlib-inline" -version = "0.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, -] - [[package]] name = "mdit-py-plugins" version = "0.4.2" @@ -1059,18 +544,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] -[[package]] -name = "mistune" -version = "3.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/1d/6b2b634e43bacc3239006e61800676aa6c41ac1836b2c57497ed27a7310b/mistune-3.1.1.tar.gz", hash = "sha256:e0740d635f515119f7d1feb6f9b192ee60f0cc649f80a8f944f905706a21654c", size = 94645 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/02/c66bdfdadbb021adb642ca4e8a5ed32ada0b4a3e4b39c5d076d19543452f/mistune-3.1.1-py3-none-any.whl", hash = "sha256:02106ac2aa4f66e769debbfa028509a275069dcffce0dfa578edd7b991ee700a", size = 53696 }, -] - [[package]] name = "myst-parser" version = "4.0.1" @@ -1088,70 +561,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579 }, ] -[[package]] -name = "nbclient" -version = "0.10.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jupyter-client" }, - { name = "jupyter-core" }, - { name = "nbformat" }, - { name = "traitlets" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434 }, -] - -[[package]] -name = "nbconvert" -version = "7.16.6" -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/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, -] - [[package]] name = "numpy" version = "2.2.3" @@ -1214,15 +623,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/7f/d322a4125405920401450118dbdc52e0384026bd669939484670ce8b2ab9/numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", size = 12839607 }, ] -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832 }, -] - [[package]] name = "packaging" version = "24.2" @@ -1232,36 +632,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, ] -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663 }, -] - -[[package]] -name = "parso" -version = "0.8.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, -] - -[[package]] -name = "pexpect" -version = "4.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, -] - [[package]] name = "platformdirs" version = "4.3.6" @@ -1280,69 +650,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] -[[package]] -name = "prometheus-client" -version = "0.21.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/14/7d0f567991f3a9af8d1cd4f619040c93b68f09a02b6d0b6ab1b2d1ded5fe/prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb", size = 78551 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/c2/ab7d37426c179ceb9aeb109a85cda8948bb269b7561a0be870cc656eefe4/prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301", size = 54682 }, -] - -[[package]] -name = "prompt-toolkit" -version = "3.0.50" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "wcwidth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 }, -] - -[[package]] -name = "psutil" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051 }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535 }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004 }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986 }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544 }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053 }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, -] - -[[package]] -name = "pycparser" -version = "2.22" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, -] - [[package]] name = "pydata-sphinx-theme" version = "0.16.1" @@ -1496,59 +803,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/82/1d96bf03ee4c0fdc3c0cbe61470070e659ca78dc0086fb88b66c185e2449/pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7", size = 46108 }, ] -[[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 } -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 }, -] - -[[package]] -name = "python-json-logger" -version = "3.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/c4/358cd13daa1d912ef795010897a483ab2f0b41c9ea1b35235a8b2f7d15a7/python_json_logger-3.2.1.tar.gz", hash = "sha256:8eb0554ea17cb75b05d2848bc14fb02fbdbd9d6972120781b974380bfa162008", size = 16287 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/72/2f30cf26664fcfa0bd8ec5ee62ec90c03bd485e4a294d92aabc76c5203a5/python_json_logger-3.2.1-py3-none-any.whl", hash = "sha256:cdc17047eb5374bd311e748b42f99d71223f3b0e186f4206cc5d52aefe85b090", size = 14924 }, -] - -[[package]] -name = "pywin32" -version = "308" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/a6/3e9f2c474895c1bb61b11fa9640be00067b5c5b363c501ee9c3fa53aec01/pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e", size = 5927028 }, - { url = "https://files.pythonhosted.org/packages/d9/b4/84e2463422f869b4b718f79eb7530a4c1693e96b8a4e5e968de38be4d2ba/pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e", size = 6558484 }, - { url = "https://files.pythonhosted.org/packages/9f/8f/fb84ab789713f7c6feacaa08dad3ec8105b88ade8d1c4f0f0dfcaaa017d6/pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c", size = 7971454 }, - { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, - { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, - { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, - { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, - { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, - { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, - { url = "https://files.pythonhosted.org/packages/a9/a4/aa562d8935e3df5e49c161b427a3a2efad2ed4e9cf81c3de636f1fdddfd0/pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed", size = 5938579 }, - { url = "https://files.pythonhosted.org/packages/c7/50/b0efb8bb66210da67a53ab95fd7a98826a97ee21f1d22949863e6d588b22/pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4", size = 6542056 }, - { url = "https://files.pythonhosted.org/packages/26/df/2b63e3e4f2df0224f8aaf6d131f54fe4e8c96400eb9df563e2aae2e1a1f9/pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd", size = 7974986 }, -] - -[[package]] -name = "pywinpty" -version = "2.0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/7c/917f9c4681bb8d34bfbe0b79d36bbcd902651aeab48790df3d30ba0202fb/pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2", size = 29017 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/b7/855db919ae526d2628f3f2e6c281c4cdff7a9a8af51bb84659a9f07b1861/pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e", size = 1405161 }, - { url = "https://files.pythonhosted.org/packages/5e/ac/6884dcb7108af66ad53f73ef4dad096e768c9203a6e6ce5e6b0c4a46e238/pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca", size = 1405249 }, - { url = "https://files.pythonhosted.org/packages/88/e5/9714def18c3a411809771a3fbcec70bffa764b9675afb00048a620fca604/pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc", size = 1405243 }, - { url = "https://files.pythonhosted.org/packages/fb/16/2ab7b3b7f55f3c6929e5f629e1a68362981e4e5fed592a2ed1cb4b4914a5/pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408", size = 1405020 }, - { url = "https://files.pythonhosted.org/packages/7c/16/edef3515dd2030db2795dbfbe392232c7a0f3dc41b98e92b38b42ba497c7/pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901", size = 1404151 }, -] - [[package]] name = "pyyaml" version = "6.0.2" @@ -1593,93 +847,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, ] -[[package]] -name = "pyzmq" -version = "26.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5a/e3/8d0382cb59feb111c252b54e8728257416a38ffcb2243c4e4775a3c990fe/pyzmq-26.2.1.tar.gz", hash = "sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca", size = 278433 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/3d/c2d9d46c033d1b51692ea49a22439f7f66d91d5c938e8b5c56ed7a2151c2/pyzmq-26.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb", size = 1345451 }, - { url = "https://files.pythonhosted.org/packages/0e/df/4754a8abcdeef280651f9bb51446c47659910940b392a66acff7c37f5cef/pyzmq-26.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641", size = 942766 }, - { url = "https://files.pythonhosted.org/packages/74/da/e6053a3b13c912eded6c2cdeee22ff3a4c33820d17f9eb24c7b6e957ffe7/pyzmq-26.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257", size = 678488 }, - { url = "https://files.pythonhosted.org/packages/9e/50/614934145244142401ca174ca81071777ab93aa88173973ba0154f491e09/pyzmq-26.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff", size = 917115 }, - { url = "https://files.pythonhosted.org/packages/80/2b/ebeb7bc4fc8e9e61650b2e09581597355a4341d413fa9b2947d7a6558119/pyzmq-26.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24", size = 874162 }, - { url = "https://files.pythonhosted.org/packages/79/48/93210621c331ad16313dc2849801411fbae10d91d878853933f2a85df8e7/pyzmq-26.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459", size = 874180 }, - { url = "https://files.pythonhosted.org/packages/f0/8b/40924b4d8e33bfdd54c1970fb50f327e39b90b902f897cf09b30b2e9ac48/pyzmq-26.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c", size = 1208139 }, - { url = "https://files.pythonhosted.org/packages/c8/b2/82d6675fc89bd965eae13c45002c792d33f06824589844b03f8ea8fc6d86/pyzmq-26.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e", size = 1520666 }, - { url = "https://files.pythonhosted.org/packages/9d/e2/5ff15f2d3f920dcc559d477bd9bb3faacd6d79fcf7c5448e585c78f84849/pyzmq-26.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3", size = 1420056 }, - { url = "https://files.pythonhosted.org/packages/40/a2/f9bbeccf7f75aa0d8963e224e5730abcefbf742e1f2ae9ea60fd9d6ff72b/pyzmq-26.2.1-cp310-cp310-win32.whl", hash = "sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa", size = 583874 }, - { url = "https://files.pythonhosted.org/packages/56/b1/44f513135843272f0e12f5aebf4af35839e2a88eb45411f2c8c010d8c856/pyzmq-26.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473", size = 647367 }, - { url = "https://files.pythonhosted.org/packages/27/9c/1bef14a37b02d651a462811bbdb1390b61cd4a5b5e95cbd7cc2d60ef848c/pyzmq-26.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594", size = 561784 }, - { url = "https://files.pythonhosted.org/packages/b9/03/5ecc46a6ed5971299f5c03e016ca637802d8660e44392bea774fb7797405/pyzmq-26.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a", size = 1346032 }, - { url = "https://files.pythonhosted.org/packages/40/51/48fec8f990ee644f461ff14c8fe5caa341b0b9b3a0ad7544f8ef17d6f528/pyzmq-26.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a", size = 943324 }, - { url = "https://files.pythonhosted.org/packages/c1/f4/f322b389727c687845e38470b48d7a43c18a83f26d4d5084603c6c3f79ca/pyzmq-26.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454", size = 678418 }, - { url = "https://files.pythonhosted.org/packages/a8/df/2834e3202533bd05032d83e02db7ac09fa1be853bbef59974f2b2e3a8557/pyzmq-26.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99", size = 915466 }, - { url = "https://files.pythonhosted.org/packages/b5/e2/45c0f6e122b562cb8c6c45c0dcac1160a4e2207385ef9b13463e74f93031/pyzmq-26.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4", size = 873347 }, - { url = "https://files.pythonhosted.org/packages/de/b9/3e0fbddf8b87454e914501d368171466a12550c70355b3844115947d68ea/pyzmq-26.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa", size = 874545 }, - { url = "https://files.pythonhosted.org/packages/1f/1c/1ee41d6e10b2127263b1994bc53b9e74ece015b0d2c0a30e0afaf69b78b2/pyzmq-26.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f", size = 1208630 }, - { url = "https://files.pythonhosted.org/packages/3d/a9/50228465c625851a06aeee97c74f253631f509213f979166e83796299c60/pyzmq-26.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba", size = 1519568 }, - { url = "https://files.pythonhosted.org/packages/c6/f2/6360b619e69da78863c2108beb5196ae8b955fe1e161c0b886b95dc6b1ac/pyzmq-26.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd", size = 1419677 }, - { url = "https://files.pythonhosted.org/packages/da/d5/f179da989168f5dfd1be8103ef508ade1d38a8078dda4f10ebae3131a490/pyzmq-26.2.1-cp311-cp311-win32.whl", hash = "sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7", size = 582682 }, - { url = "https://files.pythonhosted.org/packages/60/50/e5b2e9de3ffab73ff92bee736216cf209381081fa6ab6ba96427777d98b1/pyzmq-26.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1", size = 648128 }, - { url = "https://files.pythonhosted.org/packages/d9/fe/7bb93476dd8405b0fc9cab1fd921a08bd22d5e3016aa6daea1a78d54129b/pyzmq-26.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7", size = 562465 }, - { url = "https://files.pythonhosted.org/packages/9c/b9/260a74786f162c7f521f5f891584a51d5a42fd15f5dcaa5c9226b2865fcc/pyzmq-26.2.1-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3", size = 1348495 }, - { url = "https://files.pythonhosted.org/packages/bf/73/8a0757e4b68f5a8ccb90ddadbb76c6a5f880266cdb18be38c99bcdc17aaa/pyzmq-26.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e", size = 945035 }, - { url = "https://files.pythonhosted.org/packages/cf/de/f02ec973cd33155bb772bae33ace774acc7cc71b87b25c4829068bec35de/pyzmq-26.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8", size = 671213 }, - { url = "https://files.pythonhosted.org/packages/d1/80/8fc583085f85ac91682744efc916888dd9f11f9f75a31aef1b78a5486c6c/pyzmq-26.2.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09", size = 908750 }, - { url = "https://files.pythonhosted.org/packages/c3/25/0b4824596f261a3cc512ab152448b383047ff5f143a6906a36876415981c/pyzmq-26.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da", size = 865416 }, - { url = "https://files.pythonhosted.org/packages/a1/d1/6fda77a034d02034367b040973fd3861d945a5347e607bd2e98c99f20599/pyzmq-26.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435", size = 865922 }, - { url = "https://files.pythonhosted.org/packages/ad/81/48f7fd8a71c427412e739ce576fc1ee14f3dc34527ca9b0076e471676183/pyzmq-26.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a", size = 1201526 }, - { url = "https://files.pythonhosted.org/packages/c7/d8/818f15c6ef36b5450e435cbb0d3a51599fc884a5d2b27b46b9c00af68ef1/pyzmq-26.2.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4", size = 1512808 }, - { url = "https://files.pythonhosted.org/packages/d9/c4/b3edb7d0ae82ad6fb1a8cdb191a4113c427a01e85139906f3b655b07f4f8/pyzmq-26.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e", size = 1411836 }, - { url = "https://files.pythonhosted.org/packages/69/1c/151e3d42048f02cc5cd6dfc241d9d36b38375b4dee2e728acb5c353a6d52/pyzmq-26.2.1-cp312-cp312-win32.whl", hash = "sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a", size = 581378 }, - { url = "https://files.pythonhosted.org/packages/b6/b9/d59a7462848aaab7277fddb253ae134a570520115d80afa85e952287e6bc/pyzmq-26.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13", size = 643737 }, - { url = "https://files.pythonhosted.org/packages/55/09/f37e707937cce328944c1d57e5e50ab905011d35252a0745c4f7e5822a76/pyzmq-26.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5", size = 558303 }, - { url = "https://files.pythonhosted.org/packages/4f/2e/fa7a91ce349975971d6aa925b4c7e1a05abaae99b97ade5ace758160c43d/pyzmq-26.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23", size = 942331 }, - { url = "https://files.pythonhosted.org/packages/64/2b/1f10b34b6dc7ff4b40f668ea25ba9b8093ce61d874c784b90229b367707b/pyzmq-26.2.1-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be", size = 1345831 }, - { url = "https://files.pythonhosted.org/packages/4c/8d/34884cbd4a8ec050841b5fb58d37af136766a9f95b0b2634c2971deb09da/pyzmq-26.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399", size = 670773 }, - { url = "https://files.pythonhosted.org/packages/0f/f4/d4becfcf9e416ad2564f18a6653f7c6aa917da08df5c3760edb0baa1c863/pyzmq-26.2.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9", size = 908836 }, - { url = "https://files.pythonhosted.org/packages/07/fa/ab105f1b86b85cb2e821239f1d0900fccd66192a91d97ee04661b5436b4d/pyzmq-26.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab", size = 865369 }, - { url = "https://files.pythonhosted.org/packages/c9/48/15d5f415504572dd4b92b52db5de7a5befc76bb75340ba9f36f71306a66d/pyzmq-26.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce", size = 865676 }, - { url = "https://files.pythonhosted.org/packages/7e/35/2d91bcc7ccbb56043dd4d2c1763f24a8de5f05e06a134f767a7fb38e149c/pyzmq-26.2.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a", size = 1201457 }, - { url = "https://files.pythonhosted.org/packages/6d/bb/aa7c5119307a5762b8dca6c9db73e3ab4bccf32b15d7c4f376271ff72b2b/pyzmq-26.2.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9", size = 1513035 }, - { url = "https://files.pythonhosted.org/packages/4f/4c/527e6650c2fccec7750b783301329c8a8716d59423818afb67282304ce5a/pyzmq-26.2.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad", size = 1411881 }, - { url = "https://files.pythonhosted.org/packages/89/9f/e4412ea1b3e220acc21777a5edba8885856403d29c6999aaf00a9459eb03/pyzmq-26.2.1-cp313-cp313-win32.whl", hash = "sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb", size = 581354 }, - { url = "https://files.pythonhosted.org/packages/55/cd/f89dd3e9fc2da0d1619a82c4afb600c86b52bc72d7584953d460bc8d5027/pyzmq-26.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf", size = 643560 }, - { url = "https://files.pythonhosted.org/packages/a7/99/5de4f8912860013f1116f818a0047659bc20d71d1bc1d48f874bdc2d7b9c/pyzmq-26.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce", size = 558037 }, - { url = "https://files.pythonhosted.org/packages/06/0b/63b6d7a2f07a77dbc9768c6302ae2d7518bed0c6cee515669ca0d8ec743e/pyzmq-26.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e", size = 938580 }, - { url = "https://files.pythonhosted.org/packages/85/38/e5e2c3ffa23ea5f95f1c904014385a55902a11a67cd43c10edf61a653467/pyzmq-26.2.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891", size = 1339670 }, - { url = "https://files.pythonhosted.org/packages/d2/87/da5519ed7f8b31e4beee8f57311ec02926822fe23a95120877354cd80144/pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6", size = 660983 }, - { url = "https://files.pythonhosted.org/packages/f6/e8/1ca6a2d59562e04d326a026c9e3f791a6f1a276ebde29da478843a566fdb/pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a", size = 896509 }, - { url = "https://files.pythonhosted.org/packages/5c/e5/0b4688f7c74bea7e4f1e920da973fcd7d20175f4f1181cb9b692429c6bb9/pyzmq-26.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3", size = 853196 }, - { url = "https://files.pythonhosted.org/packages/8f/35/c17241da01195001828319e98517683dad0ac4df6fcba68763d61b630390/pyzmq-26.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e", size = 855133 }, - { url = "https://files.pythonhosted.org/packages/d2/14/268ee49bbecc3f72e225addeac7f0e2bd5808747b78c7bf7f87ed9f9d5a8/pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7", size = 1191612 }, - { url = "https://files.pythonhosted.org/packages/5e/02/6394498620b1b4349b95c534f3ebc3aef95f39afbdced5ed7ee315c49c14/pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8", size = 1500824 }, - { url = "https://files.pythonhosted.org/packages/17/fc/b79f0b72891cbb9917698add0fede71dfb64e83fa3481a02ed0e78c34be7/pyzmq-26.2.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460", size = 1399943 }, - { url = "https://files.pythonhosted.org/packages/65/d1/e630a75cfb2534574a1258fda54d02f13cf80b576d4ce6d2aa478dc67829/pyzmq-26.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d", size = 847743 }, - { url = "https://files.pythonhosted.org/packages/27/df/f94a711b4f6c4b41e227f9a938103f52acf4c2e949d91cbc682495a48155/pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99", size = 570991 }, - { url = "https://files.pythonhosted.org/packages/bf/08/0c6f97fb3c9dbfa23382f0efaf8f9aa1396a08a3358974eaae3ee659ed5c/pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c", size = 799664 }, - { url = "https://files.pythonhosted.org/packages/05/14/f4d4fd8bb8988c667845734dd756e9ee65b9a17a010d5f288dfca14a572d/pyzmq-26.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53", size = 758156 }, - { url = "https://files.pythonhosted.org/packages/e3/fe/72e7e166bda3885810bee7b23049133e142f7c80c295bae02c562caeea16/pyzmq-26.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9", size = 556563 }, -] - -[[package]] -name = "referencing" -version = "0.36.2" -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/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, -] - [[package]] name = "requests" version = "2.32.3" @@ -1695,139 +862,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, ] -[[package]] -name = "rfc3339-validator" -version = "0.1.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490 }, -] - -[[package]] -name = "rfc3986-validator" -version = "0.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242 }, -] - -[[package]] -name = "rpds-py" -version = "0.22.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/80/cce854d0921ff2f0a9fa831ba3ad3c65cee3a46711addf39a2af52df2cfd/rpds_py-0.22.3.tar.gz", hash = "sha256:e32fee8ab45d3c2db6da19a5323bc3362237c8b653c70194414b892fd06a080d", size = 26771 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/42/2a/ead1d09e57449b99dcc190d8d2323e3a167421d8f8fdf0f217c6f6befe47/rpds_py-0.22.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:6c7b99ca52c2c1752b544e310101b98a659b720b21db00e65edca34483259967", size = 359514 }, - { url = "https://files.pythonhosted.org/packages/8f/7e/1254f406b7793b586c68e217a6a24ec79040f85e030fff7e9049069284f4/rpds_py-0.22.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be2eb3f2495ba669d2a985f9b426c1797b7d48d6963899276d22f23e33d47e37", size = 349031 }, - { url = "https://files.pythonhosted.org/packages/aa/da/17c6a2c73730d426df53675ff9cc6653ac7a60b6438d03c18e1c822a576a/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70eb60b3ae9245ddea20f8a4190bd79c705a22f8028aaf8bbdebe4716c3fab24", size = 381485 }, - { url = "https://files.pythonhosted.org/packages/aa/13/2dbacd820466aa2a3c4b747afb18d71209523d353cf865bf8f4796c969ea/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4041711832360a9b75cfb11b25a6a97c8fb49c07b8bd43d0d02b45d0b499a4ff", size = 386794 }, - { url = "https://files.pythonhosted.org/packages/6d/62/96905d0a35ad4e4bc3c098b2f34b2e7266e211d08635baa690643d2227be/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:64607d4cbf1b7e3c3c8a14948b99345eda0e161b852e122c6bb71aab6d1d798c", size = 423523 }, - { url = "https://files.pythonhosted.org/packages/eb/1b/d12770f2b6a9fc2c3ec0d810d7d440f6d465ccd8b7f16ae5385952c28b89/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e69b0a0e2537f26d73b4e43ad7bc8c8efb39621639b4434b76a3de50c6966e", size = 446695 }, - { url = "https://files.pythonhosted.org/packages/4d/cf/96f1fd75512a017f8e07408b6d5dbeb492d9ed46bfe0555544294f3681b3/rpds_py-0.22.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc27863442d388870c1809a87507727b799c8460573cfbb6dc0eeaef5a11b5ec", size = 381959 }, - { url = "https://files.pythonhosted.org/packages/ab/f0/d1c5b501c8aea85aeb938b555bfdf7612110a2f8cdc21ae0482c93dd0c24/rpds_py-0.22.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e79dd39f1e8c3504be0607e5fc6e86bb60fe3584bec8b782578c3b0fde8d932c", size = 410420 }, - { url = "https://files.pythonhosted.org/packages/33/3b/45b6c58fb6aad5a569ae40fb890fc494c6b02203505a5008ee6dc68e65f7/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e0fa2d4ec53dc51cf7d3bb22e0aa0143966119f42a0c3e4998293a3dd2856b09", size = 557620 }, - { url = "https://files.pythonhosted.org/packages/83/62/3fdd2d3d47bf0bb9b931c4c73036b4ab3ec77b25e016ae26fab0f02be2af/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fda7cb070f442bf80b642cd56483b5548e43d366fe3f39b98e67cce780cded00", size = 584202 }, - { url = "https://files.pythonhosted.org/packages/04/f2/5dced98b64874b84ca824292f9cee2e3f30f3bcf231d15a903126684f74d/rpds_py-0.22.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cff63a0272fcd259dcc3be1657b07c929c466b067ceb1c20060e8d10af56f5bf", size = 552787 }, - { url = "https://files.pythonhosted.org/packages/67/13/2273dea1204eda0aea0ef55145da96a9aa28b3f88bb5c70e994f69eda7c3/rpds_py-0.22.3-cp310-cp310-win32.whl", hash = "sha256:9bd7228827ec7bb817089e2eb301d907c0d9827a9e558f22f762bb690b131652", size = 220088 }, - { url = "https://files.pythonhosted.org/packages/4e/80/8c8176b67ad7f4a894967a7a4014ba039626d96f1d4874d53e409b58d69f/rpds_py-0.22.3-cp310-cp310-win_amd64.whl", hash = "sha256:9beeb01d8c190d7581a4d59522cd3d4b6887040dcfc744af99aa59fef3e041a8", size = 231737 }, - { url = "https://files.pythonhosted.org/packages/15/ad/8d1ddf78f2805a71253fcd388017e7b4a0615c22c762b6d35301fef20106/rpds_py-0.22.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20cfb4e099748ea39e6f7b16c91ab057989712d31761d3300d43134e26e165f", size = 359773 }, - { url = "https://files.pythonhosted.org/packages/c8/75/68c15732293a8485d79fe4ebe9045525502a067865fa4278f178851b2d87/rpds_py-0.22.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:68049202f67380ff9aa52f12e92b1c30115f32e6895cd7198fa2a7961621fc5a", size = 349214 }, - { url = "https://files.pythonhosted.org/packages/3c/4c/7ce50f3070083c2e1b2bbd0fb7046f3da55f510d19e283222f8f33d7d5f4/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb4f868f712b2dd4bcc538b0a0c1f63a2b1d584c925e69a224d759e7070a12d5", size = 380477 }, - { url = "https://files.pythonhosted.org/packages/9a/e9/835196a69cb229d5c31c13b8ae603bd2da9a6695f35fe4270d398e1db44c/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bc51abd01f08117283c5ebf64844a35144a0843ff7b2983e0648e4d3d9f10dbb", size = 386171 }, - { url = "https://files.pythonhosted.org/packages/f9/8e/33fc4eba6683db71e91e6d594a2cf3a8fbceb5316629f0477f7ece5e3f75/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f3cec041684de9a4684b1572fe28c7267410e02450f4561700ca5a3bc6695a2", size = 422676 }, - { url = "https://files.pythonhosted.org/packages/37/47/2e82d58f8046a98bb9497a8319604c92b827b94d558df30877c4b3c6ccb3/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7ef9d9da710be50ff6809fed8f1963fecdfecc8b86656cadfca3bc24289414b0", size = 446152 }, - { url = "https://files.pythonhosted.org/packages/e1/78/79c128c3e71abbc8e9739ac27af11dc0f91840a86fce67ff83c65d1ba195/rpds_py-0.22.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59f4a79c19232a5774aee369a0c296712ad0e77f24e62cad53160312b1c1eaa1", size = 381300 }, - { url = "https://files.pythonhosted.org/packages/c9/5b/2e193be0e8b228c1207f31fa3ea79de64dadb4f6a4833111af8145a6bc33/rpds_py-0.22.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a60bce91f81ddaac922a40bbb571a12c1070cb20ebd6d49c48e0b101d87300d", size = 409636 }, - { url = "https://files.pythonhosted.org/packages/c2/3f/687c7100b762d62186a1c1100ffdf99825f6fa5ea94556844bbbd2d0f3a9/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e89391e6d60251560f0a8f4bd32137b077a80d9b7dbe6d5cab1cd80d2746f648", size = 556708 }, - { url = "https://files.pythonhosted.org/packages/8c/a2/c00cbc4b857e8b3d5e7f7fc4c81e23afd8c138b930f4f3ccf9a41a23e9e4/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3fb866d9932a3d7d0c82da76d816996d1667c44891bd861a0f97ba27e84fc74", size = 583554 }, - { url = "https://files.pythonhosted.org/packages/d0/08/696c9872cf56effdad9ed617ac072f6774a898d46b8b8964eab39ec562d2/rpds_py-0.22.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1352ae4f7c717ae8cba93421a63373e582d19d55d2ee2cbb184344c82d2ae55a", size = 552105 }, - { url = "https://files.pythonhosted.org/packages/18/1f/4df560be1e994f5adf56cabd6c117e02de7c88ee238bb4ce03ed50da9d56/rpds_py-0.22.3-cp311-cp311-win32.whl", hash = "sha256:b0b4136a252cadfa1adb705bb81524eee47d9f6aab4f2ee4fa1e9d3cd4581f64", size = 220199 }, - { url = "https://files.pythonhosted.org/packages/b8/1b/c29b570bc5db8237553002788dc734d6bd71443a2ceac2a58202ec06ef12/rpds_py-0.22.3-cp311-cp311-win_amd64.whl", hash = "sha256:8bd7c8cfc0b8247c8799080fbff54e0b9619e17cdfeb0478ba7295d43f635d7c", size = 231775 }, - { url = "https://files.pythonhosted.org/packages/75/47/3383ee3bd787a2a5e65a9b9edc37ccf8505c0a00170e3a5e6ea5fbcd97f7/rpds_py-0.22.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:27e98004595899949bd7a7b34e91fa7c44d7a97c40fcaf1d874168bb652ec67e", size = 352334 }, - { url = "https://files.pythonhosted.org/packages/40/14/aa6400fa8158b90a5a250a77f2077c0d0cd8a76fce31d9f2b289f04c6dec/rpds_py-0.22.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1978d0021e943aae58b9b0b196fb4895a25cc53d3956b8e35e0b7682eefb6d56", size = 342111 }, - { url = "https://files.pythonhosted.org/packages/7d/06/395a13bfaa8a28b302fb433fb285a67ce0ea2004959a027aea8f9c52bad4/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655ca44a831ecb238d124e0402d98f6212ac527a0ba6c55ca26f616604e60a45", size = 384286 }, - { url = "https://files.pythonhosted.org/packages/43/52/d8eeaffab047e6b7b7ef7f00d5ead074a07973968ffa2d5820fa131d7852/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:feea821ee2a9273771bae61194004ee2fc33f8ec7db08117ef9147d4bbcbca8e", size = 391739 }, - { url = "https://files.pythonhosted.org/packages/83/31/52dc4bde85c60b63719610ed6f6d61877effdb5113a72007679b786377b8/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22bebe05a9ffc70ebfa127efbc429bc26ec9e9b4ee4d15a740033efda515cf3d", size = 427306 }, - { url = "https://files.pythonhosted.org/packages/70/d5/1bab8e389c2261dba1764e9e793ed6830a63f830fdbec581a242c7c46bda/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3af6e48651c4e0d2d166dc1b033b7042ea3f871504b6805ba5f4fe31581d8d38", size = 442717 }, - { url = "https://files.pythonhosted.org/packages/82/a1/a45f3e30835b553379b3a56ea6c4eb622cf11e72008229af840e4596a8ea/rpds_py-0.22.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67ba3c290821343c192f7eae1d8fd5999ca2dc99994114643e2f2d3e6138b15", size = 385721 }, - { url = "https://files.pythonhosted.org/packages/a6/27/780c942de3120bdd4d0e69583f9c96e179dfff082f6ecbb46b8d6488841f/rpds_py-0.22.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:02fbb9c288ae08bcb34fb41d516d5eeb0455ac35b5512d03181d755d80810059", size = 415824 }, - { url = "https://files.pythonhosted.org/packages/94/0b/aa0542ca88ad20ea719b06520f925bae348ea5c1fdf201b7e7202d20871d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f56a6b404f74ab372da986d240e2e002769a7d7102cc73eb238a4f72eec5284e", size = 561227 }, - { url = "https://files.pythonhosted.org/packages/0d/92/3ed77d215f82c8f844d7f98929d56cc321bb0bcfaf8f166559b8ec56e5f1/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0a0461200769ab3b9ab7e513f6013b7a97fdeee41c29b9db343f3c5a8e2b9e61", size = 587424 }, - { url = "https://files.pythonhosted.org/packages/09/42/cacaeb047a22cab6241f107644f230e2935d4efecf6488859a7dd82fc47d/rpds_py-0.22.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8633e471c6207a039eff6aa116e35f69f3156b3989ea3e2d755f7bc41754a4a7", size = 555953 }, - { url = "https://files.pythonhosted.org/packages/e6/52/c921dc6d5f5d45b212a456c1f5b17df1a471127e8037eb0972379e39dff4/rpds_py-0.22.3-cp312-cp312-win32.whl", hash = "sha256:593eba61ba0c3baae5bc9be2f5232430453fb4432048de28399ca7376de9c627", size = 221339 }, - { url = "https://files.pythonhosted.org/packages/f2/c7/f82b5be1e8456600395366f86104d1bd8d0faed3802ad511ef6d60c30d98/rpds_py-0.22.3-cp312-cp312-win_amd64.whl", hash = "sha256:d115bffdd417c6d806ea9069237a4ae02f513b778e3789a359bc5856e0404cc4", size = 235786 }, - { url = "https://files.pythonhosted.org/packages/d0/bf/36d5cc1f2c609ae6e8bf0fc35949355ca9d8790eceb66e6385680c951e60/rpds_py-0.22.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ea7433ce7e4bfc3a85654aeb6747babe3f66eaf9a1d0c1e7a4435bbdf27fea84", size = 351657 }, - { url = "https://files.pythonhosted.org/packages/24/2a/f1e0fa124e300c26ea9382e59b2d582cba71cedd340f32d1447f4f29fa4e/rpds_py-0.22.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6dd9412824c4ce1aca56c47b0991e65bebb7ac3f4edccfd3f156150c96a7bf25", size = 341829 }, - { url = "https://files.pythonhosted.org/packages/cf/c2/0da1231dd16953845bed60d1a586fcd6b15ceaeb965f4d35cdc71f70f606/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20070c65396f7373f5df4005862fa162db5d25d56150bddd0b3e8214e8ef45b4", size = 384220 }, - { url = "https://files.pythonhosted.org/packages/c7/73/a4407f4e3a00a9d4b68c532bf2d873d6b562854a8eaff8faa6133b3588ec/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b09865a9abc0ddff4e50b5ef65467cd94176bf1e0004184eb915cbc10fc05c5", size = 391009 }, - { url = "https://files.pythonhosted.org/packages/a9/c3/04b7353477ab360fe2563f5f0b176d2105982f97cd9ae80a9c5a18f1ae0f/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3453e8d41fe5f17d1f8e9c383a7473cd46a63661628ec58e07777c2fff7196dc", size = 426989 }, - { url = "https://files.pythonhosted.org/packages/8d/e6/e4b85b722bcf11398e17d59c0f6049d19cd606d35363221951e6d625fcb0/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5d36399a1b96e1a5fdc91e0522544580dbebeb1f77f27b2b0ab25559e103b8b", size = 441544 }, - { url = "https://files.pythonhosted.org/packages/27/fc/403e65e56f65fff25f2973216974976d3f0a5c3f30e53758589b6dc9b79b/rpds_py-0.22.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:009de23c9c9ee54bf11303a966edf4d9087cd43a6003672e6aa7def643d06518", size = 385179 }, - { url = "https://files.pythonhosted.org/packages/57/9b/2be9ff9700d664d51fd96b33d6595791c496d2778cb0b2a634f048437a55/rpds_py-0.22.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1aef18820ef3e4587ebe8b3bc9ba6e55892a6d7b93bac6d29d9f631a3b4befbd", size = 415103 }, - { url = "https://files.pythonhosted.org/packages/bb/a5/03c2ad8ca10994fcf22dd2150dd1d653bc974fa82d9a590494c84c10c641/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f60bd8423be1d9d833f230fdbccf8f57af322d96bcad6599e5a771b151398eb2", size = 560916 }, - { url = "https://files.pythonhosted.org/packages/ba/2e/be4fdfc8b5b576e588782b56978c5b702c5a2307024120d8aeec1ab818f0/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:62d9cfcf4948683a18a9aff0ab7e1474d407b7bab2ca03116109f8464698ab16", size = 587062 }, - { url = "https://files.pythonhosted.org/packages/67/e0/2034c221937709bf9c542603d25ad43a68b4b0a9a0c0b06a742f2756eb66/rpds_py-0.22.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9253fc214112405f0afa7db88739294295f0e08466987f1d70e29930262b4c8f", size = 555734 }, - { url = "https://files.pythonhosted.org/packages/ea/ce/240bae07b5401a22482b58e18cfbabaa392409b2797da60223cca10d7367/rpds_py-0.22.3-cp313-cp313-win32.whl", hash = "sha256:fb0ba113b4983beac1a2eb16faffd76cb41e176bf58c4afe3e14b9c681f702de", size = 220663 }, - { url = "https://files.pythonhosted.org/packages/cb/f0/d330d08f51126330467edae2fa4efa5cec8923c87551a79299380fdea30d/rpds_py-0.22.3-cp313-cp313-win_amd64.whl", hash = "sha256:c58e2339def52ef6b71b8f36d13c3688ea23fa093353f3a4fee2556e62086ec9", size = 235503 }, - { url = "https://files.pythonhosted.org/packages/f7/c4/dbe1cc03df013bf2feb5ad00615038050e7859f381e96fb5b7b4572cd814/rpds_py-0.22.3-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f82a116a1d03628a8ace4859556fb39fd1424c933341a08ea3ed6de1edb0283b", size = 347698 }, - { url = "https://files.pythonhosted.org/packages/a4/3a/684f66dd6b0f37499cad24cd1c0e523541fd768576fa5ce2d0a8799c3cba/rpds_py-0.22.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3dfcbc95bd7992b16f3f7ba05af8a64ca694331bd24f9157b49dadeeb287493b", size = 337330 }, - { url = "https://files.pythonhosted.org/packages/82/eb/e022c08c2ce2e8f7683baa313476492c0e2c1ca97227fe8a75d9f0181e95/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59259dc58e57b10e7e18ce02c311804c10c5a793e6568f8af4dead03264584d1", size = 380022 }, - { url = "https://files.pythonhosted.org/packages/e4/21/5a80e653e4c86aeb28eb4fea4add1f72e1787a3299687a9187105c3ee966/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5725dd9cc02068996d4438d397e255dcb1df776b7ceea3b9cb972bdb11260a83", size = 390754 }, - { url = "https://files.pythonhosted.org/packages/37/a4/d320a04ae90f72d080b3d74597074e62be0a8ecad7d7321312dfe2dc5a6a/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b37292234e61325e7a5bb9689e55e48c3f5f603af88b1642666277a81f1fbd", size = 423840 }, - { url = "https://files.pythonhosted.org/packages/87/70/674dc47d93db30a6624279284e5631be4c3a12a0340e8e4f349153546728/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:27b1d3b3915a99208fee9ab092b8184c420f2905b7d7feb4aeb5e4a9c509b8a1", size = 438970 }, - { url = "https://files.pythonhosted.org/packages/3f/64/9500f4d66601d55cadd21e90784cfd5d5f4560e129d72e4339823129171c/rpds_py-0.22.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f612463ac081803f243ff13cccc648578e2279295048f2a8d5eb430af2bae6e3", size = 383146 }, - { url = "https://files.pythonhosted.org/packages/4d/45/630327addb1d17173adcf4af01336fd0ee030c04798027dfcb50106001e0/rpds_py-0.22.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73d3fef726b3243a811121de45193c0ca75f6407fe66f3f4e183c983573e130", size = 408294 }, - { url = "https://files.pythonhosted.org/packages/5f/ef/8efb3373cee54ea9d9980b772e5690a0c9e9214045a4e7fa35046e399fee/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3f21f0495edea7fdbaaa87e633a8689cd285f8f4af5c869f27bc8074638ad69c", size = 556345 }, - { url = "https://files.pythonhosted.org/packages/54/01/151d3b9ef4925fc8f15bfb131086c12ec3c3d6dd4a4f7589c335bf8e85ba/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1e9663daaf7a63ceccbbb8e3808fe90415b0757e2abddbfc2e06c857bf8c5e2b", size = 582292 }, - { url = "https://files.pythonhosted.org/packages/30/89/35fc7a6cdf3477d441c7aca5e9bbf5a14e0f25152aed7f63f4e0b141045d/rpds_py-0.22.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a76e42402542b1fae59798fab64432b2d015ab9d0c8c47ba7addddbaf7952333", size = 553855 }, - { url = "https://files.pythonhosted.org/packages/8f/e0/830c02b2457c4bd20a8c5bb394d31d81f57fbefce2dbdd2e31feff4f7003/rpds_py-0.22.3-cp313-cp313t-win32.whl", hash = "sha256:69803198097467ee7282750acb507fba35ca22cc3b85f16cf45fb01cb9097730", size = 219100 }, - { url = "https://files.pythonhosted.org/packages/f8/30/7ac943f69855c2db77407ae363484b915d861702dbba1aa82d68d57f42be/rpds_py-0.22.3-cp313-cp313t-win_amd64.whl", hash = "sha256:f5cf2a0c2bdadf3791b5c205d55a37a54025c6e18a71c71f82bb536cf9a454bf", size = 233794 }, - { url = "https://files.pythonhosted.org/packages/8b/63/e29f8ee14fcf383574f73b6bbdcbec0fbc2e5fc36b4de44d1ac389b1de62/rpds_py-0.22.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d48424e39c2611ee1b84ad0f44fb3b2b53d473e65de061e3f460fc0be5f1939d", size = 360786 }, - { url = "https://files.pythonhosted.org/packages/d3/e0/771ee28b02a24e81c8c0e645796a371350a2bb6672753144f36ae2d2afc9/rpds_py-0.22.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:24e8abb5878e250f2eb0d7859a8e561846f98910326d06c0d51381fed59357bd", size = 350589 }, - { url = "https://files.pythonhosted.org/packages/cf/49/abad4c4a1e6f3adf04785a99c247bfabe55ed868133e2d1881200aa5d381/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b232061ca880db21fa14defe219840ad9b74b6158adb52ddf0e87bead9e8493", size = 381848 }, - { url = "https://files.pythonhosted.org/packages/3a/7d/f4bc6d6fbe6af7a0d2b5f2ee77079efef7c8528712745659ec0026888998/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac0a03221cdb5058ce0167ecc92a8c89e8d0decdc9e99a2ec23380793c4dcb96", size = 387879 }, - { url = "https://files.pythonhosted.org/packages/13/b0/575c797377fdcd26cedbb00a3324232e4cb2c5d121f6e4b0dbf8468b12ef/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0c341fa71df5a4595f9501df4ac5abfb5a09580081dffbd1ddd4654e6e9123", size = 423916 }, - { url = "https://files.pythonhosted.org/packages/54/78/87157fa39d58f32a68d3326f8a81ad8fb99f49fe2aa7ad9a1b7d544f9478/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf9db5488121b596dbfc6718c76092fda77b703c1f7533a226a5a9f65248f8ad", size = 448410 }, - { url = "https://files.pythonhosted.org/packages/59/69/860f89996065a88be1b6ff2d60e96a02b920a262d8aadab99e7903986597/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b8db6b5b2d4491ad5b6bdc2bc7c017eec108acbf4e6785f42a9eb0ba234f4c9", size = 382841 }, - { url = "https://files.pythonhosted.org/packages/bd/d7/bc144e10d27e3cb350f98df2492a319edd3caaf52ddfe1293f37a9afbfd7/rpds_py-0.22.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b3d504047aba448d70cf6fa22e06cb09f7cbd761939fdd47604f5e007675c24e", size = 409662 }, - { url = "https://files.pythonhosted.org/packages/14/2a/6bed0b05233c291a94c7e89bc76ffa1c619d4e1979fbfe5d96024020c1fb/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:e61b02c3f7a1e0b75e20c3978f7135fd13cb6cf551bf4a6d29b999a88830a338", size = 558221 }, - { url = "https://files.pythonhosted.org/packages/11/23/cd8f566de444a137bc1ee5795e47069a947e60810ba4152886fe5308e1b7/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:e35ba67d65d49080e8e5a1dd40101fccdd9798adb9b050ff670b7d74fa41c566", size = 583780 }, - { url = "https://files.pythonhosted.org/packages/8d/63/79c3602afd14d501f751e615a74a59040328da5ef29ed5754ae80d236b84/rpds_py-0.22.3-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:26fd7cac7dd51011a245f29a2cc6489c4608b5a8ce8d75661bb4a1066c52dfbe", size = 553619 }, - { url = "https://files.pythonhosted.org/packages/9f/2e/c5c1689e80298d4e94c75b70faada4c25445739d91b94c211244a3ed7ed1/rpds_py-0.22.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:177c7c0fce2855833819c98e43c262007f42ce86651ffbb84f37883308cb0e7d", size = 233338 }, -] - -[[package]] -name = "send2trash" -version = "1.8.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, -] - -[[package]] -name = "sniffio" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, -] - [[package]] name = "snowballstemmer" version = "2.2.0" @@ -1939,46 +973,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/52/a7/d2782e4e3f77c8450f727ba74a8f12756d5ba823d81b941f1b04da9d033a/sphinxcontrib_serializinghtml-2.0.0-py3-none-any.whl", hash = "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", size = 92072 }, ] -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, -] - -[[package]] -name = "terminado" -version = "0.18.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ptyprocess", marker = "os_name != 'nt'" }, - { name = "pywinpty", marker = "os_name == 'nt'" }, - { name = "tornado" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610 }, -] - [[package]] name = "tomli" version = "2.2.1" @@ -2018,24 +1012,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, ] -[[package]] -name = "tornado" -version = "6.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 }, - { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 }, - { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 }, - { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 }, - { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 }, - { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 }, - { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 }, - { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 }, - { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 }, - { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 }, -] - [[package]] name = "tox" version = "4.24.1" @@ -2058,24 +1034,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/04/b0d1c1b44c98583cab9eabb4acdba964fdf6b6c597c53cfb8870fd08cbbf/tox-4.24.1-py3-none-any.whl", hash = "sha256:57ba7df7d199002c6df8c2db9e6484f3de6ca8f42013c083ea2d4d1e5c6bdc75", size = 171829 }, ] -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, -] - -[[package]] -name = "types-python-dateutil" -version = "2.9.0.20241206" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a9/60/47d92293d9bc521cd2301e423a358abfac0ad409b3a1606d8fbae1321961/types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb", size = 13802 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/b3/ca41df24db5eb99b00d97f89d7674a90cb6b3134c52fb8121b6d8d30f15c/types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53", size = 14384 }, -] - [[package]] name = "typing-extensions" version = "4.12.2" @@ -2085,15 +1043,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] -[[package]] -name = "uri-template" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140 }, -] - [[package]] name = "urllib3" version = "2.3.0" @@ -2117,33 +1066,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/93/fa/849483d56773ae29740ae70043ad88e068f98a6401aa819b5d6bee604683/virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a", size = 4301478 }, ] -[[package]] -name = "wcwidth" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, -] - -[[package]] -name = "webcolors" -version = "24.11.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934 }, -] - -[[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 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774 }, -] - [[package]] name = "websocket-client" version = "1.8.0" From 40a9e75229d1aaff363cc9960b5841da1e03eef3 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Wed, 19 Feb 2025 18:54:54 -0800 Subject: [PATCH 08/10] Lint and format! --- examples/basic-demo-tableload.ipynb | 1 + examples/demo-3color.ipynb | 1 + examples/demo-HiPS.ipynb | 1 + examples/demo-advanced-all.ipynb | 4 +-- examples/demo-advanced-steps.ipynb | 4 ++- examples/demo-advanced-table-images.ipynb | 1 + .../demo-advanced-tables-images-upload.ipynb | 3 +- examples/demo-basic.ipynb | 4 ++- examples/demo-lsst-footprint.ipynb | 4 ++- examples/demo-region.ipynb | 4 ++- examples/demo-show-image.ipynb | 1 + examples/filetable.py | 6 ++-- examples/multi-moc.py | 2 ++ examples/plot-interface.ipynb | 5 +++- firefly_client/__init__.py | 5 ++-- firefly_client/env.py | 5 ++-- firefly_client/fc_utils.py | 2 +- firefly_client/ffws.py | 7 +++-- firefly_client/firefly_client.py | 28 +++++++++---------- firefly_client/plot.py | 3 +- test/container.py | 3 +- test/test_5_instances_3_channels.py | 7 +++-- test/test_basic_demo_tableload.py | 8 ++++-- test/test_demo_3color.py | 7 +++-- test/test_demo_HiPS.py | 6 ++-- test/test_demo_advanced_all.py | 6 ++-- test/test_demo_advanced_steps.py | 6 ++-- test/test_demo_advanced_table_images.py | 6 ++-- ...test_demo_advanced_tables_images_upload.py | 7 +++-- test/test_demo_basic.py | 6 ++-- test/test_demo_lsst_footprint.py | 6 ++-- test/test_demo_region.py | 6 ++-- test/test_demo_show-image.py | 6 ++-- test/test_fc_version.py | 1 - test/test_plot_interface.py | 12 ++++---- test/test_simple_callback.py | 6 ++-- test/test_simple_callback_in_lab.ipynb | 1 + test/test_simple_callback_response.py | 6 ++-- test/test_socket_not_added_until_listener.py | 6 ++-- 39 files changed, 119 insertions(+), 84 deletions(-) diff --git a/examples/basic-demo-tableload.ipynb b/examples/basic-demo-tableload.ipynb index 963da68..000ebfe 100644 --- a/examples/basic-demo-tableload.ipynb +++ b/examples/basic-demo-tableload.ipynb @@ -89,6 +89,7 @@ "source": [ "import os\n", "\n", + "\n", "testdata_repo_path = (\n", " \"/hydra/cm\" # to be reset to where test files are located on your system\n", ")" diff --git a/examples/demo-3color.ipynb b/examples/demo-3color.ipynb index ef9ab27..12d7f9a 100644 --- a/examples/demo-3color.ipynb +++ b/examples/demo-3color.ipynb @@ -8,6 +8,7 @@ "source": [ "from firefly_client import FireflyClient\n", "\n", + "\n", "using_lab = False\n", "# url='https://irsa.ipac.caltech.edu/irsaviewer'\n", "url = \"http://127.0.0.1:8080/firefly\"\n", diff --git a/examples/demo-HiPS.ipynb b/examples/demo-HiPS.ipynb index e6db737..e751bbd 100644 --- a/examples/demo-HiPS.ipynb +++ b/examples/demo-HiPS.ipynb @@ -31,6 +31,7 @@ "source": [ "from firefly_client import FireflyClient\n", "\n", + "\n", "using_lab = False\n", "url = \"http://127.0.0.1:8080/firefly\"\n", "# FireflyClient._debug= True" diff --git a/examples/demo-advanced-all.ipynb b/examples/demo-advanced-all.ipynb index b23bbb4..6d46570 100644 --- a/examples/demo-advanced-all.ipynb +++ b/examples/demo-advanced-all.ipynb @@ -22,8 +22,9 @@ "metadata": {}, "outputs": [], "source": [ + "\n", "from firefly_client import FireflyClient\n", - "import astropy.utils.data\n", + "\n", "\n", "using_lab = True\n", "url = \"http://127.0.0.1:8080/firefly\"\n", @@ -53,7 +54,6 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", "# os.environ" ] }, diff --git a/examples/demo-advanced-steps.ipynb b/examples/demo-advanced-steps.ipynb index be645eb..cd2cf30 100644 --- a/examples/demo-advanced-steps.ipynb +++ b/examples/demo-advanced-steps.ipynb @@ -23,9 +23,11 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = True\n", "url = \"http://127.0.0.1:8080/firefly\"\n", "# FireflyClient._debug= True" diff --git a/examples/demo-advanced-table-images.ipynb b/examples/demo-advanced-table-images.ipynb index 85b1fc1..0f8194c 100644 --- a/examples/demo-advanced-table-images.ipynb +++ b/examples/demo-advanced-table-images.ipynb @@ -32,6 +32,7 @@ "source": [ "from firefly_client import FireflyClient\n", "\n", + "\n", "using_lab = False\n", "url = \"http://127.0.0.1:8080/firefly\"\n", "# FireflyClient._debug= True" diff --git a/examples/demo-advanced-tables-images-upload.ipynb b/examples/demo-advanced-tables-images-upload.ipynb index 9b0fee8..2338415 100644 --- a/examples/demo-advanced-tables-images-upload.ipynb +++ b/examples/demo-advanced-tables-images-upload.ipynb @@ -30,8 +30,9 @@ "metadata": {}, "outputs": [], "source": [ + "\n", "from firefly_client import FireflyClient\n", - "import astropy.utils.data\n", + "\n", "\n", "using_lab = False\n", "url = \"http://127.0.0.1:8080/firefly\"\n", diff --git a/examples/demo-basic.ipynb b/examples/demo-basic.ipynb index 7bbfaf8..f83bdcb 100644 --- a/examples/demo-basic.ipynb +++ b/examples/demo-basic.ipynb @@ -36,9 +36,11 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", "# url = 'https://irsa.ipac.caltech.edu/irsaviewer'\n", "url = \"http://127.0.0.1:8080/firefly\"\n", diff --git a/examples/demo-lsst-footprint.ipynb b/examples/demo-lsst-footprint.ipynb index c1c5c1b..9257ae9 100644 --- a/examples/demo-lsst-footprint.ipynb +++ b/examples/demo-lsst-footprint.ipynb @@ -36,9 +36,11 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", "url = \"http://127.0.0.1:8080/firefly\"\n", "# FireflyClient._debug= True" diff --git a/examples/demo-region.ipynb b/examples/demo-region.ipynb index f87f146..323086d 100644 --- a/examples/demo-region.ipynb +++ b/examples/demo-region.ipynb @@ -36,9 +36,11 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import astropy.utils.data\n", "\n", + "from firefly_client import FireflyClient\n", + "\n", + "\n", "using_lab = False\n", "url = \"http://127.0.0.1:8080/firefly\"\n", "# FireflyClient._debug= True" diff --git a/examples/demo-show-image.ipynb b/examples/demo-show-image.ipynb index 3348fe6..087372b 100644 --- a/examples/demo-show-image.ipynb +++ b/examples/demo-show-image.ipynb @@ -8,6 +8,7 @@ "source": [ "from firefly_client import FireflyClient\n", "\n", + "\n", "using_lab = False\n", "url = \"http://127.0.0.1:8080/firefly\"\n", "# FireflyClient._debug= True" diff --git a/examples/filetable.py b/examples/filetable.py index 32b031e..04fafdd 100755 --- a/examples/filetable.py +++ b/examples/filetable.py @@ -1,11 +1,11 @@ #!/usr/bin/env python -from argparse import ArgumentParser, HelpFormatter -import csv -from glob import glob import os +import csv import tempfile import textwrap +from glob import glob +from argparse import HelpFormatter, ArgumentParser import firefly_client diff --git a/examples/multi-moc.py b/examples/multi-moc.py index bb24586..341d5d8 100644 --- a/examples/multi-moc.py +++ b/examples/multi-moc.py @@ -1,6 +1,8 @@ import astropy.utils.data + from firefly_client import FireflyClient + fc = FireflyClient.make_client( "http://127.0.0.1:8080/firefly", channel_override="moc-channel" ) diff --git a/examples/plot-interface.ipynb b/examples/plot-interface.ipynb index 1d324ee..e49c184 100644 --- a/examples/plot-interface.ipynb +++ b/examples/plot-interface.ipynb @@ -39,8 +39,9 @@ "metadata": {}, "outputs": [], "source": [ - "from firefly_client import FireflyClient\n", "import firefly_client.plot as ffplt\n", + "from firefly_client import FireflyClient\n", + "\n", "\n", "fc = FireflyClient.make_client(\"https://irsa.ipac.caltech.edu/irsaviewer\")\n", "ffplt.use_client(fc)" @@ -267,6 +268,7 @@ "source": [ "import astropy.io.fits as fits\n", "\n", + "\n", "m31_image_fname = astropy.utils.data.download_file(\n", " \"http://web.ipac.caltech.edu/staff/roby/demo/2mass-m31-green.fits\",\n", " timeout=120,\n", @@ -407,6 +409,7 @@ "source": [ "from astropy.table import Table\n", "\n", + "\n", "wise_table = Table.read(wise_tbl_name, format=\"ipac\")" ] }, diff --git a/firefly_client/__init__.py b/firefly_client/__init__.py index 30fe29e..49a6d1c 100644 --- a/firefly_client/__init__.py +++ b/firefly_client/__init__.py @@ -1,9 +1,10 @@ from importlib.metadata import PackageNotFoundError, version -from .firefly_client import FireflyClient -from .ffws import FFWs from .env import Env +from .ffws import FFWs from .range_values import RangeValues +from .firefly_client import FireflyClient + try: __version__ = version("firefly_client") diff --git a/firefly_client/env.py b/firefly_client/env.py index 8debba3..ede90ee 100644 --- a/firefly_client/env.py +++ b/firefly_client/env.py @@ -1,7 +1,8 @@ -import base64 -import datetime import os import uuid +import base64 +import datetime + try: from .fc_utils import str_2_bool diff --git a/firefly_client/fc_utils.py b/firefly_client/fc_utils.py index de065d7..669cf80 100644 --- a/firefly_client/fc_utils.py +++ b/firefly_client/fc_utils.py @@ -1,5 +1,5 @@ -import base64 import json +import base64 import mimetypes import urllib.parse from typing import TypeVar diff --git a/firefly_client/ffws.py b/firefly_client/ffws.py index 8eeeaf1..9f30827 100644 --- a/firefly_client/ffws.py +++ b/firefly_client/ffws.py @@ -1,19 +1,20 @@ -import _thread import json import time +import _thread import traceback from copy import deepcopy from urllib.parse import urljoin + try: from .env import Env except ImportError: from env import Env try: - from .fc_utils import ALL, debug, DebugMarker, dict_to_str, warn + from .fc_utils import ALL, DebugMarker, warn, debug, dict_to_str except ImportError: - from fc_utils import ALL, debug, DebugMarker, dict_to_str, warn + from fc_utils import ALL, DebugMarker, warn, debug, dict_to_str MAX_CHANNELS = 3 diff --git a/firefly_client/firefly_client.py b/firefly_client/firefly_client.py index ab1466e..59082b9 100644 --- a/firefly_client/firefly_client.py +++ b/firefly_client/firefly_client.py @@ -7,8 +7,8 @@ import json import math -import socket import time +import socket import weakref import webbrowser from copy import copy @@ -32,29 +32,29 @@ from range_values import RangeValues try: from .fc_utils import ( - ACTION_DICT, ALL, - create_image_url, - debug, + ACTION_DICT, + LO_VIEW_DICT, DebugMarker, - dict_to_str, + warn, + debug, ensure3, + dict_to_str, gen_item_id, - LO_VIEW_DICT, - warn, + create_image_url, ) except ImportError: from fc_utils import ( - ACTION_DICT, ALL, - create_image_url, - debug, + ACTION_DICT, + LO_VIEW_DICT, DebugMarker, - dict_to_str, + warn, + debug, ensure3, + dict_to_str, gen_item_id, - LO_VIEW_DICT, - warn, + create_image_url, ) __docformat__ = "restructuredtext" @@ -521,7 +521,7 @@ def display_url(self, url=None): except NameError: ipy_str = "" if "zmqshell" in ipy_str: - from IPython.display import display, HTML + from IPython.display import HTML, display display( HTML( diff --git a/firefly_client/plot.py b/firefly_client/plot.py index 1d90be1..420e5ae 100644 --- a/firefly_client/plot.py +++ b/firefly_client/plot.py @@ -7,13 +7,14 @@ variable FIREFLY_URL. """ -import logging import os +import logging import tempfile from .fc_utils import gen_item_id from .firefly_client import FireflyClient + logger = logging.getLogger(__name__) fc = None diff --git a/test/container.py b/test/container.py index 82a4f31..cde9a96 100644 --- a/test/container.py +++ b/test/container.py @@ -1,7 +1,8 @@ import random +from pytest_container.inspect import PortForwarding, NetworkProtocol from pytest_container.container import Container, EntrypointSelection -from pytest_container.inspect import NetworkProtocol, PortForwarding + FIREFLY_CONTAINER = Container( url="docker.io/ipac/firefly:latest", diff --git a/test/test_5_instances_3_channels.py b/test/test_5_instances_3_channels.py index 51b05d5..7286cc0 100644 --- a/test/test_5_instances_3_channels.py +++ b/test/test_5_instances_3_channels.py @@ -1,12 +1,13 @@ import time import pytest - -from firefly_client import FireflyClient -from pytest_container.container import ContainerData from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_5_instances_3_channels(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_basic_demo_tableload.py b/test/test_basic_demo_tableload.py index 5296cd8..a964be9 100644 --- a/test/test_basic_demo_tableload.py +++ b/test/test_basic_demo_tableload.py @@ -1,14 +1,16 @@ import os import time -from pathlib import Path from urllib import request +from pathlib import Path import pytest -from firefly_client import FireflyClient -from pytest_container.container import ContainerData from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_basic_tableload(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_3color.py b/test/test_demo_3color.py index ad7e68b..6454e5c 100644 --- a/test/test_demo_3color.py +++ b/test/test_demo_3color.py @@ -1,12 +1,13 @@ import time import pytest - -from firefly_client import FireflyClient -from pytest_container.container import ContainerData from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_3color(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_HiPS.py b/test/test_demo_HiPS.py index b7bc2dd..f24ff6f 100644 --- a/test/test_demo_HiPS.py +++ b/test/test_demo_HiPS.py @@ -1,11 +1,13 @@ import time import pytest -from firefly_client import FireflyClient -from pytest_container.container import ContainerData from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_hi_ps(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_advanced_all.py b/test/test_demo_advanced_all.py index 84a697f..fa59993 100644 --- a/test/test_demo_advanced_all.py +++ b/test/test_demo_advanced_all.py @@ -1,11 +1,13 @@ import time import pytest -from firefly_client import FireflyClient -from pytest_container.container import ContainerData from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_advanced_steps.py b/test/test_demo_advanced_steps.py index 81c6335..5cca559 100644 --- a/test/test_demo_advanced_steps.py +++ b/test/test_demo_advanced_steps.py @@ -1,14 +1,14 @@ import time import pytest +from pytest_mock import MockerFixture from astropy.utils.data import download_file - -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv_steps(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_advanced_table_images.py b/test/test_demo_advanced_table_images.py index dbc9740..e0efe8b 100644 --- a/test/test_demo_advanced_table_images.py +++ b/test/test_demo_advanced_table_images.py @@ -1,14 +1,14 @@ import time import pytest - -from firefly_client import FireflyClient +from pytest_mock import MockerFixture from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test import firefly_slate_demo as fs from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv_table_imgs(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_advanced_tables_images_upload.py b/test/test_demo_advanced_tables_images_upload.py index 40c5eec..f204678 100644 --- a/test/test_demo_advanced_tables_images_upload.py +++ b/test/test_demo_advanced_tables_images_upload.py @@ -1,13 +1,14 @@ import time import pytest - -from firefly_client import FireflyClient -from pytest_container.container import ContainerData from pytest_mock import MockerFixture +from pytest_container.container import ContainerData + from test import firefly_slate_demo as fs from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_adv_table_imgs_upload(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_basic.py b/test/test_demo_basic.py index 09841a6..9e472ab 100644 --- a/test/test_demo_basic.py +++ b/test/test_demo_basic.py @@ -1,14 +1,14 @@ import time import pytest +from pytest_mock import MockerFixture from astropy.utils.data import download_file - -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_basics(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_lsst_footprint.py b/test/test_demo_lsst_footprint.py index 24cd7d5..c62f509 100644 --- a/test/test_demo_lsst_footprint.py +++ b/test/test_demo_lsst_footprint.py @@ -1,14 +1,14 @@ import time import pytest +from pytest_mock import MockerFixture from astropy.utils.data import download_file - -from firefly_client import FireflyClient from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_lsst_footprint(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_demo_region.py b/test/test_demo_region.py index 1da1b5f..a517c41 100644 --- a/test/test_demo_region.py +++ b/test/test_demo_region.py @@ -1,12 +1,12 @@ import time -from pytest_mock import MockerFixture -from test.container import FIREFLY_CONTAINER - import pytest +from pytest_mock import MockerFixture from astropy.utils.data import download_file from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + from firefly_client import FireflyClient diff --git a/test/test_demo_show-image.py b/test/test_demo_show-image.py index 8a5c305..4657546 100644 --- a/test/test_demo_show-image.py +++ b/test/test_demo_show-image.py @@ -1,13 +1,13 @@ import time import pytest - -from firefly_client import FireflyClient +from pytest_mock import MockerFixture from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_show_img(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_fc_version.py b/test/test_fc_version.py index 039fec0..42c1e55 100644 --- a/test/test_fc_version.py +++ b/test/test_fc_version.py @@ -1,5 +1,4 @@ import firefly_client -from firefly_client import FireflyClient def test_version(): diff --git a/test/test_plot_interface.py b/test/test_plot_interface.py index 5af9d22..1a413a6 100644 --- a/test/test_plot_interface.py +++ b/test/test_plot_interface.py @@ -1,16 +1,18 @@ import time -from pytest_mock import MockerFixture -from test.container import FIREFLY_CONTAINER - import pytest from astropy.io import fits +from pytest_mock import MockerFixture from astropy.table import Table from astropy.utils.data import download_file from pytest_container.container import ContainerData -from firefly_client import FireflyClient -from firefly_client import plot as ffplt +from test.container import FIREFLY_CONTAINER + +from firefly_client import ( + FireflyClient, + plot as ffplt, +) @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) diff --git a/test/test_simple_callback.py b/test/test_simple_callback.py index 6b0e038..43f48ae 100644 --- a/test/test_simple_callback.py +++ b/test/test_simple_callback.py @@ -1,11 +1,11 @@ import time -from pytest_mock import MockerFixture -from test.container import FIREFLY_CONTAINER - import pytest +from pytest_mock import MockerFixture from pytest_container.container import ContainerData +from test.container import FIREFLY_CONTAINER + from firefly_client import FireflyClient diff --git a/test/test_simple_callback_in_lab.ipynb b/test/test_simple_callback_in_lab.ipynb index 5cd989e..5224711 100644 --- a/test/test_simple_callback_in_lab.ipynb +++ b/test/test_simple_callback_in_lab.ipynb @@ -117,6 +117,7 @@ "source": [ "from firefly_client import __version__ as v\n", "\n", + "\n", "v" ] }, diff --git a/test/test_simple_callback_response.py b/test/test_simple_callback_response.py index 62fab0b..cb66be9 100644 --- a/test/test_simple_callback_response.py +++ b/test/test_simple_callback_response.py @@ -1,13 +1,13 @@ import time import pytest - -from firefly_client import FireflyClient +from pytest_mock import MockerFixture from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_simple_callback_response(container: ContainerData, mocker: MockerFixture): diff --git a/test/test_socket_not_added_until_listener.py b/test/test_socket_not_added_until_listener.py index 1c14bb3..14742c3 100644 --- a/test/test_socket_not_added_until_listener.py +++ b/test/test_socket_not_added_until_listener.py @@ -1,13 +1,13 @@ import time import pytest - -from firefly_client import FireflyClient +from pytest_mock import MockerFixture from pytest_container.container import ContainerData -from pytest_mock import MockerFixture from test.container import FIREFLY_CONTAINER +from firefly_client import FireflyClient + @pytest.mark.parametrize("container", [FIREFLY_CONTAINER], indirect=["container"]) def test_socket_not_added_until_listener( From 4b01c7520867591d06e029f029cb7fc45f53f0d3 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Wed, 19 Feb 2025 19:01:45 -0800 Subject: [PATCH 09/10] Fix the `build_docs` tox test --- pyproject.toml | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5a60c24..7168c64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -148,25 +148,9 @@ allowlist_externals = ["python"] description = "invoke sphinx-build to build the HTML docs" change_dir = "docs" dependency_groups = ["docs"] -commands = [ - [ - "sphinx-build", - "-j", - "auto", - "--color", - "-W", - "--keep-going", - "-b", - "html", - "-d", - "_build/.doctrees", - ".", - "_build/html", - "{posargs}", - ], -] +commands = [["make", "html"]] uv_python_preference = "only-managed" -allowlist_externals = ["python"] +allowlist_externals = ["python", "make", "uv"] [tool.ruff] # Exclude a variety of commonly ignored directories. From 6c11a10d32bcf8078e15d9f91cbc0bda6ff5fcb0 Mon Sep 17 00:00:00 2001 From: Annie Ehler Date: Thu, 20 Feb 2025 13:03:37 -0800 Subject: [PATCH 10/10] Update the gitignore and configure setup tools to ignore the pytest coverage maps --- .gitignore | 1 + pyproject.toml | 7 +++++-- uv.lock | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f8cd4a6..fd53ec7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,6 +43,7 @@ htmlcov/ .cache nosetests.xml coverage.xml +coverage/ *,cover .hypothesis/ diff --git a/pyproject.toml b/pyproject.toml index 4eba947..da3e6de 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,13 +55,16 @@ test = [ "tox>=4.24.1", ] +[tool.setuptools] +packages = ["firefly_client"] + [tool.pytest.ini_options] testpaths = ["firefly_client", "docs", "test"] doctest_plus = "enabled" text_file_format = "rst" addopts = [ "--doctest-rst", - "-n 4", + "--numprocesses=4", "--import-mode=importlib", "--cov=firefly_client", "--cov-append", @@ -117,7 +120,7 @@ commands = [ [ "pytest", "--doctest-rst", - "-n 4", + "--numprocesses=4", "--import-mode=importlib", "--cov=firefly_client", "--cov-append", diff --git a/uv.lock b/uv.lock index bedc0a6..801132b 100644 --- a/uv.lock +++ b/uv.lock @@ -364,7 +364,7 @@ wheels = [ [[package]] name = "firefly-client" -version = "3.2.0" +version = "3.3.0" source = { editable = "." } dependencies = [ { name = "astropy", version = "6.1.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },