From 87c295ed1cb8c246f41d902a326d893740ec6ab0 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Fri, 7 Aug 2026 23:23:09 +0200 Subject: [PATCH 01/30] build(deps): source cleopatra from git main for the 0.29 restructure cleopatra 0.29 restructures its flat modules into subpackages with no re-export shim, so the imports move. Track git main (0.29.0) via a pixi pypi-dependency ahead of the PyPI/conda-forge release; the published [viz] extra stays a version spec so built wheels carry no direct-URL metadata. Bump the [viz] floor to >=0.29.0 and drop the git source once cleopatra 0.29 is released. --- pixi.lock | 207 ++++++++++++++++++++++++------------------------- pyproject.toml | 6 ++ 2 files changed, 109 insertions(+), 104 deletions(-) diff --git a/pixi.lock b/pixi.lock index 7d82eb242e..406ff9433f 100644 --- a/pixi.lock +++ b/pixi.lock @@ -137,6 +137,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -146,7 +147,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/a0/2d/43c8522a2038e9d0e7dbdf3a61195ecc31ca576fb1527a528c877e87d973/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl linux-aarch64: @@ -253,6 +253,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl @@ -262,7 +263,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl osx-64: @@ -363,6 +363,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl @@ -373,7 +374,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda @@ -473,6 +473,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl @@ -482,7 +483,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl win-64: @@ -577,6 +577,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl @@ -586,7 +587,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl dev: @@ -803,6 +803,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -931,7 +932,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -1167,6 +1167,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1294,7 +1295,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -1525,6 +1525,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1652,7 +1653,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -1881,6 +1881,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -2007,7 +2008,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -2232,6 +2232,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -2352,7 +2353,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -2596,6 +2596,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2717,7 +2718,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl @@ -2951,6 +2951,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3071,7 +3072,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/d6/24e3b5906abb0b674ff2ae195bc3ce59708df2bcd17cf17703b2d7dd643a/lxml-6.1.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -3300,6 +3300,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3424,7 +3425,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl @@ -3650,6 +3650,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3773,7 +3774,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl @@ -3995,6 +3995,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -4111,7 +4112,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -4307,6 +4307,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4427,7 +4428,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -4615,6 +4615,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4734,7 +4735,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -4917,6 +4917,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5036,7 +5037,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -5217,6 +5217,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5335,7 +5336,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -5514,6 +5514,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5627,7 +5628,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -5868,6 +5868,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5989,7 +5990,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -6222,6 +6222,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6342,7 +6343,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -6570,6 +6570,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6690,7 +6691,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -6916,6 +6916,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7035,7 +7036,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -7257,6 +7257,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7371,7 +7372,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -7559,6 +7559,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7688,7 +7689,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -7868,6 +7868,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7996,7 +7997,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/21/359880af3ea9b7cb23bea5b51e8e70ef3866c03be09da9a2787e18e330a8/pandas-3.0.5-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -8170,6 +8170,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8296,7 +8297,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -8470,6 +8470,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8597,7 +8598,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -8766,6 +8766,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8887,7 +8888,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -9077,6 +9077,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl @@ -9205,7 +9206,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -9384,6 +9384,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9510,7 +9511,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/51/fac252f4a913ed5eabf3c11b880a9e8d5a6c10f0b2129d0462212d238b4d/pandas-3.0.5-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -9684,6 +9684,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9808,7 +9809,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -9982,6 +9982,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10106,7 +10107,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -10276,6 +10276,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10397,7 +10398,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -10585,6 +10585,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10707,7 +10708,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e3/70/4363150359f95b4cb4bcbb34ca23572bb5495749a621a8f3d5a1ddfd293c/pandas-3.0.5-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl @@ -10892,6 +10892,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11020,7 +11021,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -11193,6 +11193,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11318,7 +11319,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -11492,6 +11492,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11617,7 +11618,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -11787,6 +11787,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11907,7 +11908,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -12098,6 +12098,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12223,7 +12224,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -12407,6 +12407,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12532,7 +12533,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl @@ -12710,6 +12710,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12835,7 +12836,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/58/87ef68ac83f4c7690961bce288fd8e382bc5f1513860fc7f90a9c1c1c6bf/imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -13011,6 +13011,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -13136,7 +13137,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/6d/b53b99a9f2766d095985947a5782f1702cabb129a34f7a802d7197af832f/tzdata-2026.3-py2.py3-none-any.whl @@ -13308,6 +13308,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ + - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -13428,7 +13429,6 @@ environments: - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl @@ -14510,7 +14510,7 @@ packages: - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=compressed-mapping + - pkg:pypi/backports-zstd?source=hash-mapping run_exports: {} size: 239892 timestamp: 1781450817988 @@ -17265,7 +17265,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=compressed-mapping + - pkg:pypi/numpy?source=hash-mapping run_exports: weak: - numpy >=1.25,<3 @@ -17311,7 +17311,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=compressed-mapping + - pkg:pypi/numpy?source=hash-mapping run_exports: weak: - numpy >=1.25,<3 @@ -18003,7 +18003,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping run_exports: {} size: 17104066 timestamp: 1781912972195 @@ -18314,7 +18314,7 @@ packages: - python_abi 3.13.* *_cp313 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 117625 timestamp: 1785422127987 @@ -18328,7 +18328,7 @@ packages: - python_abi 3.14.* *_cp314 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 119163 timestamp: 1785422126684 @@ -19438,7 +19438,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/gdal?source=compressed-mapping + - pkg:pypi/gdal?source=hash-mapping run_exports: {} size: 2447911 timestamp: 1784038283198 @@ -22627,7 +22627,7 @@ packages: - python_abi 3.11.* *_cp311 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 118914 timestamp: 1785422173475 @@ -22641,7 +22641,7 @@ packages: - python_abi 3.12.* *_cp312 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 117427 timestamp: 1785422157504 @@ -22669,7 +22669,7 @@ packages: - python_abi 3.14.* *_cp314 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 119267 timestamp: 1785422152533 @@ -22811,7 +22811,7 @@ packages: license: PSF-2.0 license_family: PSF purls: - - pkg:pypi/aiohappyeyeballs?source=compressed-mapping + - pkg:pypi/aiohappyeyeballs?source=hash-mapping run_exports: {} size: 24690 timestamp: 1782986823268 @@ -22898,8 +22898,7 @@ packages: depends: - python >=3.14 license: BSD-3-Clause AND MIT AND EPL-2.0 - purls: - - pkg:pypi/backports-zstd?source=compressed-mapping + purls: [] run_exports: {} size: 7526 timestamp: 1781450817767 @@ -22958,7 +22957,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/cached-property?source=compressed-mapping + - pkg:pypi/cached-property?source=hash-mapping run_exports: {} size: 14752 timestamp: 1783242913845 @@ -22969,7 +22968,7 @@ packages: - python >=3.10 license: ISC purls: - - pkg:pypi/certifi?source=compressed-mapping + - pkg:pypi/certifi?source=hash-mapping run_exports: {} size: 137015 timestamp: 1784717699092 @@ -22981,7 +22980,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/charset-normalizer?source=compressed-mapping + - pkg:pypi/charset-normalizer?source=hash-mapping run_exports: {} size: 61418 timestamp: 1783505332569 @@ -22996,7 +22995,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/click?source=compressed-mapping + - pkg:pypi/click?source=hash-mapping run_exports: {} size: 106227 timestamp: 1783085395110 @@ -23010,7 +23009,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/click?source=compressed-mapping + - pkg:pypi/click?source=hash-mapping run_exports: {} size: 107155 timestamp: 1783085363526 @@ -23056,7 +23055,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/dask?source=compressed-mapping + - pkg:pypi/dask?source=hash-mapping run_exports: {} size: 1074755 timestamp: 1784031128995 @@ -23188,7 +23187,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/h2?source=compressed-mapping + - pkg:pypi/h2?source=hash-mapping run_exports: {} size: 100184 timestamp: 1784898236952 @@ -23200,7 +23199,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/hpack?source=compressed-mapping + - pkg:pypi/hpack?source=hash-mapping run_exports: {} size: 32884 timestamp: 1782283986153 @@ -23225,7 +23224,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/idna?source=compressed-mapping + - pkg:pypi/idna?source=hash-mapping run_exports: {} size: 163869 timestamp: 1781620148226 @@ -23683,7 +23682,7 @@ packages: - obstore >=0.5.1 license: MIT purls: - - pkg:pypi/zarr?source=compressed-mapping + - pkg:pypi/zarr?source=hash-mapping run_exports: {} size: 471664 timestamp: 1785511642744 @@ -23708,7 +23707,7 @@ packages: license: MIT license_family: MIT purls: - - pkg:pypi/zipp?source=compressed-mapping + - pkg:pypi/zipp?source=hash-mapping run_exports: {} size: 24190 timestamp: 1779159948016 @@ -27747,7 +27746,7 @@ packages: - python_abi 3.11.* *_cp311 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 114187 timestamp: 1785422578909 @@ -27760,7 +27759,7 @@ packages: - python_abi 3.12.* *_cp312 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 112703 timestamp: 1785422534356 @@ -27773,7 +27772,7 @@ packages: - python_abi 3.13.* *_cp313 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 113971 timestamp: 1785422522880 @@ -27786,7 +27785,7 @@ packages: - python_abi 3.14.* *_cp314 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 115207 timestamp: 1785422575221 @@ -31180,7 +31179,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/pandas?source=compressed-mapping + - pkg:pypi/pandas?source=hash-mapping run_exports: {} size: 14402418 timestamp: 1784821943837 @@ -32005,7 +32004,7 @@ packages: - python_abi 3.11.* *_cp311 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 114759 timestamp: 1785422729160 @@ -32033,7 +32032,7 @@ packages: - python_abi 3.13.* *_cp313 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 114819 timestamp: 1785422632081 @@ -32205,7 +32204,7 @@ packages: license: MIT AND Apache-2.0 license_family: Apache purls: - - pkg:pypi/aiohttp?source=compressed-mapping + - pkg:pypi/aiohttp?source=hash-mapping run_exports: {} size: 1033675 timestamp: 1784900600588 @@ -32229,7 +32228,7 @@ packages: license: MIT AND Apache-2.0 license_family: Apache purls: - - pkg:pypi/aiohttp?source=compressed-mapping + - pkg:pypi/aiohttp?source=hash-mapping run_exports: {} size: 1041930 timestamp: 1784900597803 @@ -32718,7 +32717,7 @@ packages: - zstd >=1.5.7,<1.6.0a0 license: BSD-3-Clause AND MIT AND EPL-2.0 purls: - - pkg:pypi/backports-zstd?source=compressed-mapping + - pkg:pypi/backports-zstd?source=hash-mapping run_exports: {} size: 238542 timestamp: 1781450836106 @@ -34992,7 +34991,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/msgpack?source=compressed-mapping + - pkg:pypi/msgpack?source=hash-mapping run_exports: {} size: 89054 timestamp: 1782460807428 @@ -35008,7 +35007,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/msgpack?source=compressed-mapping + - pkg:pypi/msgpack?source=hash-mapping run_exports: {} size: 89413 timestamp: 1782460810590 @@ -35249,7 +35248,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=compressed-mapping + - pkg:pypi/numpy?source=hash-mapping run_exports: weak: - numpy >=1.25,<3 @@ -35398,7 +35397,7 @@ packages: - zstandard >=0.23.0 license: BSD-3-Clause purls: - - pkg:pypi/pandas?source=compressed-mapping + - pkg:pypi/pandas?source=hash-mapping run_exports: {} size: 14103773 timestamp: 1785276287020 @@ -35488,7 +35487,7 @@ packages: license: Apache-2.0 license_family: APACHE purls: - - pkg:pypi/propcache?source=compressed-mapping + - pkg:pypi/propcache?source=hash-mapping run_exports: {} size: 49101 timestamp: 1780037831647 @@ -35946,7 +35945,7 @@ packages: license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/scipy?source=compressed-mapping + - pkg:pypi/scipy?source=hash-mapping run_exports: {} size: 15248913 timestamp: 1781914321655 @@ -36094,7 +36093,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/tornado?source=compressed-mapping + - pkg:pypi/tornado?source=hash-mapping run_exports: {} size: 865801 timestamp: 1781006895319 @@ -36284,7 +36283,7 @@ packages: - vc14_runtime >=14.44.35208 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 116290 timestamp: 1785422205402 @@ -36299,7 +36298,7 @@ packages: - vc14_runtime >=14.44.35208 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 114221 timestamp: 1785422189861 @@ -36314,7 +36313,7 @@ packages: - vc14_runtime >=14.44.35208 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 115201 timestamp: 1785422193029 @@ -36329,7 +36328,7 @@ packages: - vc14_runtime >=14.44.35208 license: BSD-2-Clause purls: - - pkg:pypi/wrapt?source=compressed-mapping + - pkg:pypi/wrapt?source=hash-mapping run_exports: {} size: 115942 timestamp: 1785422199912 @@ -36419,7 +36418,7 @@ packages: license: Apache-2.0 license_family: Apache purls: - - pkg:pypi/yarl?source=compressed-mapping + - pkg:pypi/yarl?source=hash-mapping run_exports: {} size: 167471 timestamp: 1784526549850 @@ -36483,6 +36482,19 @@ packages: - pystac-client>=0.8.0 ; extra == 'stac' - stac-asset>=0.4.7 ; extra == 'stac' requires_python: '>=3.11,<4' +- pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + name: cleopatra + version: 0.29.0 + requires_dist: + - numpy>=2.0.0 + - matplotlib>=3.9 + - pillow>=12.1.1 + - imageio-ffmpeg>=0.4.9 + - hpc-utils>=0.1.4 + - pyproj>=3.7.2 ; extra == 'tiles' + - xyzservices>=2026.3.0 ; extra == 'tiles' + - cmap>=0.7.2 ; extra == 'science-colors' + requires_python: '>=3.11,<4' - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl name: traitlets version: 5.16.0 @@ -43448,19 +43460,6 @@ packages: requires_dist: - typing-extensions>=4.12.0 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/dc/c6/c993e8f3129975c2a60c87c4489d432ad203390c04f606f19b9ec2ad5d58/cleopatra-0.28.0-py3-none-any.whl - name: cleopatra - version: 0.28.0 - sha256: df0434d5501c5aa85eddc2cba4466437608d325aa47d27639d442a583b79155e - requires_dist: - - hpc-utils>=0.1.4 - - imageio-ffmpeg>=0.4.9 - - matplotlib>=3.9 - - numpy>=2.0.0 - - pillow>=12.1.1 ; extra == 'tiles' - - pyproj>=3.7.2 ; extra == 'tiles' - - xyzservices>=2026.3.0 ; extra == 'tiles' - requires_python: '>=3.11,<4' - pypi: https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl name: fonttools version: 4.63.0 diff --git a/pyproject.toml b/pyproject.toml index a57e618c61..3c462caebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -296,6 +296,12 @@ platforms = ["win-64", "linux-64", "linux-aarch64", "osx-arm64", "osx-64"] [tool.pixi.pypi-dependencies] pyramids-gis = { path = ".", editable = true } +# Dev/CI track cleopatra's restructured main (0.29) ahead of its PyPI/conda-forge +# release; git main (0.29.0) satisfies the published [viz] extra's `>=0.28.0`, so the +# git source wins in every dev/test/docs env while the built wheel keeps a plain +# version spec (PyPI rejects direct-URL metadata). Remove this and bump the [viz] +# floor to `>=0.29.0` once cleopatra 0.29 is released. +cleopatra = { git = "https://github.com/serapeum-org/cleopatra.git", branch = "main", extras = ["tiles"] } [tool.pixi.tasks] main = { cmd = "pytest -vvv --cov=src/pyramids -sv -m 'not plot and not xarray' --cov-report=xml", description = "Run the main test suite" } From 011d4357c19b386f1d76b38b6c6de305c8684a21 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Fri, 7 Aug 2026 23:23:09 +0200 Subject: [PATCH 02/30] refactor(plot): migrate to cleopatra 0.29's restructured API cleopatra 0.29 moved its flat modules into subpackages (glyphs/{gridded,primitives, stats}, styling/, basemap/) and re-exports nothing from the root, so every import path changes: - array_glyph -> glyphs.gridded.array_glyph (ArrayGlyph/FrameLabel/PointOverlay); ColorBar -> styling.colorbar; mesh_glyph/vector_glyph -> glyphs.gridded.*; scatter_glyph/polygon_glyph -> glyphs.primitives.*; colors/styles -> styling.*; geo/reference/tiles -> basemap.*; statistical_glyph.StatisticalGlyph -> glyphs.stats.histogram_glyph.HistogramGlyph (renamed). Config path is unchanged. - Update the pyramids.plot lazy shim and every test importorskip/@patch target and the cleopatra >=0.28 gates to >=0.29. Adopt the two behaviours the restructure unblocked: - ArrayGlyph.facet now accepts colorbar=ColorBar (cleopatra#271), so fold the loose cbar_* into a ColorBar on the facet path like plot/animate and drop the RENDER_ONLY_OVERRIDES workaround. - MeshGlyph.plot now accepts colorbar=ColorBar, so plot_mesh_data takes a typed ColorBar (was bool-only). Refs #933, #934. --- src/pyramids/base/_utils.py | 8 +-- src/pyramids/basemap/__init__.py | 6 +-- src/pyramids/basemap/basemap.py | 18 +++---- src/pyramids/dataset/_plot_helpers.py | 53 +++++++++----------- src/pyramids/dataset/abstract_dataset.py | 2 +- src/pyramids/dataset/collection.py | 8 +-- src/pyramids/dataset/dataset.py | 2 +- src/pyramids/dataset/engines/analysis.py | 32 ++++++------ src/pyramids/dataset/engines/bands.py | 2 +- src/pyramids/feature/_plot.py | 4 +- src/pyramids/feature/collection.py | 8 +-- src/pyramids/netcdf/_plot.py | 10 ++-- src/pyramids/netcdf/netcdf.py | 8 +-- src/pyramids/netcdf/plot_options.py | 12 ++--- src/pyramids/netcdf/ugrid/dataset.py | 4 +- src/pyramids/netcdf/ugrid/plot.py | 27 ++++++---- src/pyramids/plot.py | 17 ++++--- tests/base/test_dtype_conversions.py | 2 +- tests/basemap/test_basemap.py | 48 +++++++++--------- tests/dataset/plot/test_basemap_dispatch.py | 10 ++-- tests/dataset/plot/test_new_render_params.py | 30 +++++------ tests/dataset/plot/test_plot_cleanups.py | 4 +- tests/dataset/plot/test_plot_dataset.py | 12 +++-- tests/dataset/plot/test_plot_engine.py | 32 ++++++------ tests/dataset/plot/test_plot_facade.py | 6 +-- tests/dataset/plot/test_plot_specs.py | 12 ++--- tests/feature/test_plot_cleopatra.py | 8 ++- tests/netcdf/plot/test_plot_animate.py | 2 +- tests/netcdf/plot/test_plot_coords.py | 4 +- tests/netcdf/plot/test_plot_facet.py | 4 +- tests/netcdf/plot/test_plot_lazy.py | 2 +- tests/netcdf/plot/test_plot_render.py | 2 +- tests/netcdf/plot/test_plot_selectors.py | 2 +- tests/ugrid/test_plot.py | 21 +++++++- 34 files changed, 225 insertions(+), 197 deletions(-) diff --git a/src/pyramids/base/_utils.py b/src/pyramids/base/_utils.py index 23fae28d5c..a5ef5de59d 100644 --- a/src/pyramids/base/_utils.py +++ b/src/pyramids/base/_utils.py @@ -654,14 +654,14 @@ def require_optional(module_name: str, message: str, *, return_module: bool = Fa One nuance for dotted names: `import_basemap` previously spelled `from cleopatra import tiles`, which calls `__import__("cleopatra", ..., fromlist=("tiles",))`. Here it becomes - `__import__("cleopatra.tiles")`, so the parent package resolves internally + `__import__("cleopatra.basemap.tiles")`, so the parent package resolves internally rather than through `builtins.__import__`. A patch keyed on `name == "cleopatra"` no longer intercepts it; key on the dotted name instead. No in-tree test depends on this. Args: module_name: Dotted module path to import, e.g. ``"zarr"`` or - ``"cleopatra.tiles"``. + ``"cleopatra.basemap.tiles"``. message: The install hint raised when the import fails. Compose it with :func:`lazy_extra_hint` for the ``[lazy]`` extra. return_module: When `True` the imported module object is returned so the @@ -894,8 +894,8 @@ def import_h5py(message: str): def import_basemap(message: str): - """Import the web-tile basemap backend (``cleopatra.tiles``, the ``[tiles]`` extra).""" - return require_optional("cleopatra.tiles", message) + """Import the web-tile basemap backend (``cleopatra.basemap.tiles``, the ``[tiles]`` extra).""" + return require_optional("cleopatra.basemap.tiles", message) def ogr_ds_to_gdal_dataset(ogr_ds: ogr.DataSource) -> gdal.Dataset: diff --git a/src/pyramids/basemap/__init__.py b/src/pyramids/basemap/__init__.py index 20e54ff3af..bdae5e1425 100644 --- a/src/pyramids/basemap/__init__.py +++ b/src/pyramids/basemap/__init__.py @@ -3,7 +3,7 @@ Adds web-tile basemaps (OpenStreetMap, CartoDB, Esri, etc.) underneath data plotted on matplotlib axes. :func:`~pyramids.basemap.add_basemap` and :func:`~pyramids.basemap.get_provider` are thin wrappers over -:mod:`cleopatra.tiles` (the cleopatra C-6 helpers) — cleopatra does the +:mod:`cleopatra.basemap.tiles` (the cleopatra C-6 helpers) — cleopatra does the tile fetching, stitching, GDAL CRS warping, and rendering. The tile functions require the cleopatra ``[tiles]`` extra (which pins @@ -13,8 +13,8 @@ - conda-forge: ``conda install -c conda-forge pyramids-viz`` Natural Earth vector layers and the global relief raster have moved to -:mod:`cleopatra.reference` (the viz layer), where they belong with the rest of -the map-decoration helpers — use ``cleopatra.reference`` for those backdrops. +:mod:`cleopatra.basemap.reference` (the viz layer), where they belong with the rest of +the map-decoration helpers — use ``cleopatra.basemap.reference`` for those backdrops. """ from pyramids.basemap.basemap import add_basemap, get_provider diff --git a/src/pyramids/basemap/basemap.py b/src/pyramids/basemap/basemap.py index 1b9d02ca07..63f82de34b 100644 --- a/src/pyramids/basemap/basemap.py +++ b/src/pyramids/basemap/basemap.py @@ -1,7 +1,7 @@ -"""Web-tile basemap helpers — thin wrappers over ``cleopatra.tiles``. +"""Web-tile basemap helpers — thin wrappers over ``cleopatra.basemap.tiles``. :func:`add_basemap` and :func:`get_provider` here delegate to -:func:`cleopatra.tiles.add_tiles` and :func:`cleopatra.tiles.get_provider` +:func:`cleopatra.basemap.tiles.add_tiles` and :func:`cleopatra.basemap.tiles.get_provider` (the cleopatra C-6 helpers, shipped in ``cleopatra >= 0.8.0`` and pinned via the ``[viz]`` extra as ``cleopatra[tiles]``). All the real work — provider resolution, zoom selection, parallel tile fetching, stitching, @@ -40,7 +40,7 @@ def get_provider(name: str | None = None) -> Any: """Resolve a web-tile provider by name. - Thin wrapper over :func:`cleopatra.tiles.get_provider`. + Thin wrapper over :func:`cleopatra.basemap.tiles.get_provider`. Args: name (str or None, optional): @@ -84,10 +84,10 @@ def get_provider(name: str | None = None) -> Any: See Also: add_basemap: Uses ``get_provider`` internally (via cleopatra) when ``source`` is a string. - cleopatra.tiles.get_provider: The underlying implementation. + cleopatra.basemap.tiles.get_provider: The underlying implementation. """ import_basemap(_BASEMAP_MSG) - from cleopatra.tiles import get_provider as _get_provider + from cleopatra.basemap.tiles import get_provider as _get_provider return _get_provider(name) @@ -106,7 +106,7 @@ def add_basemap( ) -> Any: """Add a web-tile basemap underneath the data plotted on ``ax``. - Thin wrapper over :func:`cleopatra.tiles.add_tiles`: fetches XYZ web + Thin wrapper over :func:`cleopatra.basemap.tiles.add_tiles`: fetches XYZ web tiles for the axes' geographic extent, stitches them into a single image, reprojects to the data's CRS via GDAL when ``crs`` is not Web Mercator, and renders the image beneath the data layer. @@ -146,7 +146,7 @@ def add_basemap( Returns: matplotlib.axes.Axes: The axes with the basemap added (whatever - :func:`cleopatra.tiles.add_tiles` returns). + :func:`cleopatra.basemap.tiles.add_tiles` returns). Raises: OptionalPackageDoesNotExist: If the cleopatra ``[tiles]`` extra is @@ -181,10 +181,10 @@ def add_basemap( See Also: get_provider: Resolve a tile provider name to a ``TileProvider``. - cleopatra.tiles.add_tiles: The underlying implementation. + cleopatra.basemap.tiles.add_tiles: The underlying implementation. """ import_basemap(_BASEMAP_MSG) - from cleopatra.tiles import add_tiles + from cleopatra.basemap.tiles import add_tiles result = add_tiles( ax, diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index 590cfdc323..121f9cfa90 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -78,7 +78,7 @@ from pyramids.basemap import basemap as _basemap_module if TYPE_CHECKING: - from cleopatra.geo import Basemap + from cleopatra.basemap.geo import Basemap # N-6 — Mesh rendering shares this module's "data in, glyph out" # contract via :func:`mesh_render`. The function lives next to @@ -370,7 +370,7 @@ def render_array( animation_axis_values: Frame labels for the animation path. Required when ``mode == "animate"``. data_getter: Optional callable ``f(i) -> ndarray`` forwarded to - :meth:`cleopatra.array_glyph.ArrayGlyph.animate` as the + :meth:`cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.animate` as the ``data_getter`` kwarg. When set the animation streams each frame lazily through this callback instead of slicing the pre-materialised ``arr`` stack — used by @@ -388,7 +388,7 @@ def render_array( non-empty contextily provider string adds a pyramids web-tile basemap underneath the rendered plot (tile mode is applied on ``"plot"`` and per-panel on ``"facet"``). A - :class:`cleopatra.geo.Basemap` is cleopatra's relief/features + :class:`cleopatra.basemap.geo.Basemap` is cleopatra's relief/features reference layer, forwarded to the glyph's own ``basemap=`` on the ``"plot"``/``"animate"`` render call; it is **not** supported on ``"facet"`` (raises). A ``dict`` is a deprecated alias translated to @@ -405,8 +405,8 @@ def render_array( Returns: The result object cleopatra returns for that mode — typically a - :class:`cleopatra.array_glyph.ArrayGlyph` for ``"plot"`` and - ``"animate"``, and a :class:`cleopatra.array_glyph.FacetGrid` + :class:`cleopatra.glyphs.gridded.array_glyph.ArrayGlyph` for ``"plot"`` and + ``"animate"``, and a :class:`cleopatra.glyphs.gridded.array_glyph.FacetGrid` for ``"facet"``. Raises: @@ -415,7 +415,7 @@ def render_array( truthy and ``basemap_epsg`` is ``None``, if a cleopatra ``Basemap`` (or equivalent dict) is passed on the ``"facet"`` path, or if ``color_scale`` is not a recognised - :class:`~cleopatra.styles.ColorScale` value. + :class:`~cleopatra.styling.styles.ColorScale` value. Examples: - Single-slice plot path. Tagged ``+SKIP`` because the call @@ -531,22 +531,18 @@ def render_array( ``` """ require_cleopatra() - from cleopatra.array_glyph import ArrayGlyph, ColorBar, PointOverlay - from cleopatra.geo import Basemap - from cleopatra.styles import ColorScale + from cleopatra.basemap.geo import Basemap + from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, PointOverlay + from cleopatra.styling.colorbar import ColorBar + from cleopatra.styling.styles import ColorScale # Deprecate + translate the loose plot kwargs to the typed specs (cbar_* -> # ColorBar, point_*/pid_* -> PointOverlay, dict basemap -> Basemap) so every - # raster plot/animate call is steered onto the typed API in one place and - # cleopatra only ever sees the typed form. The facet path is skipped: - # cleopatra's ``ArrayGlyph.facet`` does not accept ``colorbar=ColorBar`` / - # ``PointOverlay`` (only the loose ``cbar_*`` kwargs, like the mesh glyph), so - # faceting keeps the loose forms — there is no typed alternative there. - # Once cleopatra#256 lands (``ArrayGlyph.facet`` accepts ``colorbar=ColorBar``), - # drop this facet exception (and the ``cbar_*`` in ``RENDER_ONLY_OVERRIDES`` - # below) so facet folds the loose kwargs like plot/animate. Tracked in #934. - if mode != "facet": - _migrate_deprecated_plot_specs(kwargs, ColorBar, PointOverlay) + # raster plot/animate/facet call is steered onto the typed API in one place and + # cleopatra only ever sees the typed form. cleopatra >= 0.29 accepts + # ``colorbar=ColorBar`` on ``ArrayGlyph.facet`` too (serapeum-org/cleopatra#271), + # so faceting folds the loose kwargs like plot/animate — no exception. + _migrate_deprecated_plot_specs(kwargs, ColorBar, PointOverlay) if isinstance(basemap, dict) and basemap: warnings.warn( "Passing a dict as basemap= is deprecated; pass " @@ -660,13 +656,10 @@ def render_array( # render method only overwrites ``default_options["title"]`` when its # ``title`` arg is not ``None``, so a constructor-set title survives and # routing it to the constructor (via ``option_keys()``) is correct. - # ``kind`` is force-routed to the render call. The deprecated loose cbar_* - # kwargs are too — but only the ``facet`` path still carries them here (plot / - # animate already folded them into ``colorbar=ColorBar(...)`` above, so this is - # a no-op there). cleopatra's ``facet`` renders them only when they reach the - # facet *call* (its per-panel constructors), so force-routing keeps the loose - # cbar_* rendering on facet instead of being dropped on the parent constructor. - RENDER_ONLY_OVERRIDES = {"kind", *_CBAR_TO_COLORBAR} + # ``kind`` is force-routed to the render call. The loose cbar_* kwargs no longer + # need force-routing: every mode (incl. facet, on cleopatra >= 0.29) folds them + # into ``colorbar=ColorBar(...)`` above, so they never reach the split. + RENDER_ONLY_OVERRIDES = {"kind"} # Reuse the option set resolved for the style/hillshade guard above — it is # cleopatra's declared constructor options and does not change within a call. ctor_option_keys = option_keys @@ -717,7 +710,7 @@ def render_array( # with pyramids' pre-existing web-tile ``basemap=``, so dispatch on the type: # - a ``str`` provider name (or ``True``) is a pyramids web-tile basemap # drawn under the raster -- pyramids owns this via ``add_basemap``; - # - a ``cleopatra.geo.Basemap`` is cleopatra's relief/features reference + # - a ``cleopatra.basemap.geo.Basemap`` is cleopatra's relief/features reference # layer, forwarded to the glyph's own ``basemap=`` on the render call. # (A non-empty ``dict`` was already deprecated + translated to a # ``Basemap`` up front, so only an empty ``{}`` reaches here — no basemap.) @@ -824,7 +817,7 @@ def mesh_render( """N-6 — sibling of :func:`render_array` for UGRID mesh data. Routes a pyramids ``Mesh2d`` + a per-element data array through - cleopatra's :class:`~cleopatra.mesh_glyph.MeshGlyph`, returning the + cleopatra's :class:`~cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph`, returning the glyph instance. Mirrors the :func:`render_array` contract — "single backend abstraction, one entry point per cleopatra glyph" — so the raster facade (:meth:`pyramids.dataset.Dataset.plot`, @@ -852,7 +845,7 @@ def mesh_render( ``edgecolor``, ``colorbar``, ``title``. Returns: - cleopatra.mesh_glyph.MeshGlyph: The same instance that + cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph: The same instance that :func:`pyramids.netcdf.ugrid.plot.plot_mesh_data` returns. Raises: @@ -906,7 +899,7 @@ def mesh_render( if basemap and basemap_epsg is None: raise ValueError("Dataset must have a CRS (epsg) to use basemap.") require_cleopatra() - from cleopatra.mesh_glyph import MeshGlyph + from cleopatra.glyphs.gridded.mesh_glyph import MeshGlyph from pyramids.netcdf.ugrid.plot import plot_mesh_data diff --git a/src/pyramids/dataset/abstract_dataset.py b/src/pyramids/dataset/abstract_dataset.py index 368548ddec..4a03e5d5c5 100644 --- a/src/pyramids/dataset/abstract_dataset.py +++ b/src/pyramids/dataset/abstract_dataset.py @@ -1116,7 +1116,7 @@ def plot( ``DeprecationWarning``). ``False`` hides the bar, ``None`` uses the default. color_scale (str, optional): Color-scale mode. One of "linear", "power", "sym-lognorm", "boundary-norm", "midpoint" - (case-insensitive), or a ``cleopatra.styles.ColorScale`` member. Integer codes are no + (case-insensitive), or a ``cleopatra.styling.styles.ColorScale`` member. Integer codes are no longer accepted. The default is "linear". gamma (float, optional): Exponent for the "power" color scale. The default is 1./2. diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index c880137784..b51f6d5f84 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -46,8 +46,8 @@ from pyramids.feature import FeatureCollection if TYPE_CHECKING: - from cleopatra.array_glyph import ArrayGlyph, FrameLabel - from cleopatra.geo import Basemap + from cleopatra.basemap.geo import Basemap + from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, FrameLabel from dask.delayed import Delayed @@ -2512,12 +2512,12 @@ def plot( | Parameter | Type | Description | |----------------------------|-----------------------|-------------| | animation_axis_values | sequence, optional | Per-frame labels for the animation, one per timestep. Defaults to the collection's `time` axis when set (e.g. dates parsed by `read_multiple_files`), else `range(time_length)` (index labels). Pass a sequence here to override (e.g. `range(2000, 2024)`). | - | points | array \| PointOverlay | Point overlay. A 3-column array (value to display, row index, column index) draws unstyled points. To style them, pass a `cleopatra.array_glyph.PointOverlay(points, color=..., size=..., label_color=..., label_size=...)` instead — on cleopatra >= 0.26 the loose `point_color` / `point_size` / `pid_color` / `pid_size` kwargs are deprecated; set the styling on the `PointOverlay` instead. | + | points | array \| PointOverlay | Point overlay. A 3-column array (value to display, row index, column index) draws unstyled points. To style them, pass a `cleopatra.glyphs.gridded.array_glyph.PointOverlay(points, color=..., size=..., label_color=..., label_size=...)` instead — on cleopatra >= 0.26 the loose `point_color` / `point_size` / `pid_color` / `pid_size` kwargs are deprecated; set the styling on the `PointOverlay` instead. | | figsize | tuple, optional | Figure size. Default is `(8, 8)`. | | title | str, optional | Title of the plot. Default is `'Total Discharge'`. | | title_size | int, optional | Title size. Default is `15`. | | colorbar | bool \| ColorBar, optional | Colour-bar spec `pyramids.plot.ColorBar(label=…, length=…, orientation=…, label_size=…, label_rotation=…, label_location=…, ticks_spacing=…)` (cleopatra >= 0.28). The loose `cbar_*` / `ticks_spacing` kwargs it replaces are deprecated — still accepted, but they emit a `DeprecationWarning`. `False` hides it, `None` uses the default. | - | color_scale | str, optional | Color-scale mode (default `"linear"`): one of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styles.ColorScale` member. Integer codes are no longer accepted. | + | color_scale | str, optional | Color-scale mode (default `"linear"`): one of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styling.styles.ColorScale` member. Integer codes are no longer accepted. | | gamma | float, optional | Exponent for `color_scale="power"`. Default is `1/2`. | | line_threshold | float, optional | `linthresh` for `color_scale="sym-lognorm"`. Default is `0.0001`. | | line_scale | float, optional | `linscale` for `color_scale="sym-lognorm"`. Default is `0.001`. | diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index c2ef400a09..bcc050f75a 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -232,7 +232,7 @@ def _remap_nodata_to(arr: np.ndarray, src_nd: Any, dst_nd: Any) -> np.typing.NDA if TYPE_CHECKING: import dask.array as da - from cleopatra.geo import Basemap + from cleopatra.basemap.geo import Basemap from geopandas import GeoDataFrame diff --git a/src/pyramids/dataset/engines/analysis.py b/src/pyramids/dataset/engines/analysis.py index cf21a579bd..de69322668 100644 --- a/src/pyramids/dataset/engines/analysis.py +++ b/src/pyramids/dataset/engines/analysis.py @@ -28,8 +28,8 @@ from pyramids.feature import FeatureCollection if TYPE_CHECKING: - from cleopatra.array_glyph import ArrayGlyph - from cleopatra.geo import Basemap + from cleopatra.basemap.geo import Basemap + from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph from pyramids.dataset.dataset import Dataset @@ -1674,7 +1674,7 @@ def plot_histogram( """Plot the value distribution of a band as a histogram. Backed by cleopatra's - :class:`~cleopatra.statistical_glyph.StatisticalGlyph`. The band is + :class:`~cleopatra.glyphs.stats.histogram_glyph.HistogramGlyph`. The band is read into memory, the band's no-data value and ``exclude_value`` (and any ``NaN`` for floating-point bands) are dropped, and only the remaining valid samples reach the glyph. Requires the ``[viz]`` extra. @@ -1690,15 +1690,15 @@ def plot_histogram( ax (matplotlib.axes.Axes, optional): Axes to draw on. A new figure/axes is created when ``None``. **kwargs: - Style options forwarded to the ``StatisticalGlyph`` + Style options forwarded to the ``HistogramGlyph`` constructor, filtered via - :meth:`StatisticalGlyph.filter_kwargs` so only accepted keys + :meth:`HistogramGlyph.filter_kwargs` so only accepted keys are passed. Returns: tuple: ``(fig, ax, hist)`` from - :meth:`StatisticalGlyph.histogram` — the + :meth:`HistogramGlyph.histogram` — the :class:`matplotlib.figure.Figure`, the :class:`matplotlib.axes.Axes`, and the histogram ``dict``. @@ -1729,7 +1729,7 @@ def plot_histogram( ``` """ require_cleopatra() - from cleopatra.statistical_glyph import StatisticalGlyph + from cleopatra.glyphs.stats.histogram_glyph import HistogramGlyph arr = self._ds.read_array(band=band).flatten() no_data_value = self._ds.no_data_value[band] @@ -1748,9 +1748,7 @@ def plot_histogram( f"Band {band} has no valid samples to histogram after masking " "no-data / exclude_value / NaN." ) - glyph = StatisticalGlyph( - values, ax=ax, **StatisticalGlyph.filter_kwargs(kwargs) - ) + glyph = HistogramGlyph(values, ax=ax, **HistogramGlyph.filter_kwargs(kwargs)) result = glyph.histogram(bins=bins) return result @@ -1804,7 +1802,7 @@ def to_image( ``` """ require_cleopatra() - from cleopatra.array_glyph import ArrayGlyph + from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph arr = self._ds.read_array(band=band) no_data_value = self._ds.no_data_value[band] @@ -1842,7 +1840,7 @@ def plot_vector_field( Reads ``u_band`` and ``v_band`` as the vector components over the dataset's cell-centre coordinate grid (built from the geotransform) and renders them via cleopatra's - :class:`~cleopatra.vector_glyph.VectorGlyph` as arrows, wind barbs, + :class:`~cleopatra.glyphs.gridded.vector_glyph.VectorGlyph` as arrows, wind barbs, or streamlines, coloured by vector magnitude. Requires the ``[viz]`` extra. @@ -1905,7 +1903,7 @@ def plot_vector_field( ``` """ require_cleopatra() - from cleopatra.vector_glyph import VectorGlyph + from cleopatra.glyphs.gridded.vector_glyph import VectorGlyph band_count = self._ds.band_count for name, idx in (("u_band", u_band), ("v_band", v_band)): @@ -1965,7 +1963,7 @@ def plot( An explicit ``cmap`` / ``color_scale`` opts out. The plot function uses the `cleopatra` as a backend to plot the raster data, for more information check - [ArrayGlyph](https://serapeum-org.github.io/cleopatra/latest/api/array-glyph-class/#cleopatra.array_glyph.ArrayGlyph.plot). + [ArrayGlyph](https://serapeum-org.github.io/cleopatra/latest/api/array-glyph-class/#cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.plot). Implementation note: this method is a thin caller around the shared :func:`pyramids.dataset._plot_helpers.render_array` @@ -2025,7 +2023,7 @@ def plot( | `figsize` | tuple, optional | Figure size. Default is `(8, 8)`. | | `title` | str, optional | Title of the plot. Default is `'Total Discharge'`. | | `title_size` | int, optional | Title size. Default is `15`. | - | `color_scale` | str, optional | Color-scale mode. One of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styles.ColorScale` member. Integer codes are no longer accepted. Default is `"linear"`. | + | `color_scale` | str, optional | Color-scale mode. One of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styling.styles.ColorScale` member. Integer codes are no longer accepted. Default is `"linear"`. | | `gamma` | float, optional | Exponent for the `"power"` color scale. Default is `1/2`. | | `line_threshold` | float, optional | `linthresh` for the `"sym-lognorm"` color scale. Default is `0.0001`. | | `line_scale` | float, optional | `linscale` for the `"sym-lognorm"` color scale. Default is `0.001`. | @@ -2224,7 +2222,7 @@ def _read_plot_lazy_array(self, band: int, chunks: Any) -> np.ndarray: @staticmethod def _process_color_table(color_table: DataFrame) -> DataFrame: require_cleopatra() - from cleopatra.colors import Colors + from cleopatra.styling.colors import Colors # if the color_table does not contain the red, green, and blue columns, assume it has one column with # the color as hex and then, convert the color to rgb. @@ -2284,7 +2282,7 @@ def _palette_colormap(color_table: DataFrame) -> tuple[Any, list[float]]: boundary edges, sorted by colour-table value. """ require_cleopatra() - from cleopatra.colors import category_boundaries + from cleopatra.styling.colors import category_boundaries from matplotlib.colors import BoundaryNorm, ListedColormap processed = Analysis._process_color_table(color_table).sort_values("values") diff --git a/src/pyramids/dataset/engines/bands.py b/src/pyramids/dataset/engines/bands.py index 59307547d1..3438c09dc4 100644 --- a/src/pyramids/dataset/engines/bands.py +++ b/src/pyramids/dataset/engines/bands.py @@ -832,7 +832,7 @@ def _set_color_table(self, color_df: DataFrame, overwrite: bool = False) -> None True to overwrite the existing color table. Default is False. """ require_cleopatra() - from cleopatra.colors import Colors + from cleopatra.styling.colors import Colors color = Colors(color_df["color"].tolist()) color_rgb = color.to_rgb(normalized=False) diff --git a/src/pyramids/feature/_plot.py b/src/pyramids/feature/_plot.py index f434c55649..f077af20bf 100644 --- a/src/pyramids/feature/_plot.py +++ b/src/pyramids/feature/_plot.py @@ -81,7 +81,7 @@ def plot_cleopatra(fc: Any, column: str | None = None, **kwargs: Any) -> Any: def scatter_glyph(fc: Any, values: Any, **kwargs: Any) -> Any: """Build a cleopatra ScatterGlyph from `fc`'s point coordinates.""" require_cleopatra() - from cleopatra.scatter_glyph import ScatterGlyph + from cleopatra.glyphs.primitives.scatter_glyph import ScatterGlyph return ScatterGlyph( fc.geometry.x.to_numpy(), @@ -94,7 +94,7 @@ def scatter_glyph(fc: Any, values: Any, **kwargs: Any) -> Any: def polygon_glyph(fc: Any, values: Any, **kwargs: Any) -> Any: """Build a cleopatra PolygonGlyph from `fc`'s polygon exterior rings.""" require_cleopatra() - from cleopatra.polygon_glyph import PolygonGlyph + from cleopatra.glyphs.primitives.polygon_glyph import PolygonGlyph polygons: list = [] poly_values: list | None = [] if values is not None else None diff --git a/src/pyramids/feature/collection.py b/src/pyramids/feature/collection.py index 442288b4b3..6b97eba39c 100644 --- a/src/pyramids/feature/collection.py +++ b/src/pyramids/feature/collection.py @@ -2202,8 +2202,8 @@ def plot( :meth:`geopandas.GeoDataFrame.plot` and return the matplotlib ``Axes``. This is the long-standing behaviour and is unchanged. - ``"cleopatra"``: render polygons through - :class:`~cleopatra.polygon_glyph.PolygonGlyph` or points through - :class:`~cleopatra.scatter_glyph.ScatterGlyph` — sharing the + :class:`~cleopatra.glyphs.primitives.polygon_glyph.PolygonGlyph` or points through + :class:`~cleopatra.glyphs.primitives.scatter_glyph.ScatterGlyph` — sharing the colour/colorbar styling of the raster glyph path — and return the cleopatra glyph. Requires the ``[viz]`` extra. @@ -2295,7 +2295,7 @@ def _cleopatra_scatter_glyph(self, values: Any, **kwargs: Any) -> Any: **kwargs: Style options, filtered to the glyph's accepted keys. Returns: - cleopatra.scatter_glyph.ScatterGlyph: The point glyph. + cleopatra.glyphs.primitives.scatter_glyph.ScatterGlyph: The point glyph. """ return _plot.scatter_glyph(self, values, **kwargs) @@ -2313,7 +2313,7 @@ def _cleopatra_polygon_glyph(self, values: Any, **kwargs: Any) -> Any: **kwargs: Style options, filtered to the glyph's accepted keys. Returns: - cleopatra.polygon_glyph.PolygonGlyph: The polygon glyph. + cleopatra.glyphs.primitives.polygon_glyph.PolygonGlyph: The polygon glyph. """ return _plot.polygon_glyph(self, values, **kwargs) diff --git a/src/pyramids/netcdf/_plot.py b/src/pyramids/netcdf/_plot.py index 657b6d1e4a..c4cd0d537d 100644 --- a/src/pyramids/netcdf/_plot.py +++ b/src/pyramids/netcdf/_plot.py @@ -20,7 +20,7 @@ from pyramids.netcdf.plot_options import ColorOpts, FacetSpec, Selectors if TYPE_CHECKING: - from cleopatra.geo import Basemap + from cleopatra.basemap.geo import Basemap from pyramids.netcdf.netcdf import NetCDF @@ -544,8 +544,8 @@ def _remove_colorbar(result: Any) -> None: Honours the xarray-aligned ``add_colorbar=False`` switch on :meth:`NetCDF.plot`. Cleopatra always attaches a colorbar to its - :class:`~cleopatra.array_glyph.ArrayGlyph` / - :class:`~cleopatra.array_glyph.FacetGrid` results, so pyramids + :class:`~cleopatra.glyphs.gridded.array_glyph.ArrayGlyph` / + :class:`~cleopatra.glyphs.gridded.array_glyph.FacetGrid` results, so pyramids applies the removal here after the render returns. The helper is defensive — it leaves ``result`` untouched when no ``.cbar`` attribute exists, when the attribute is already @@ -706,7 +706,7 @@ def _build_facet_stack( Returns: tuple: ``(stack, facet_kwargs)`` — the materialised array and the kwargs dict to forward to - :meth:`cleopatra.array_glyph.ArrayGlyph.facet`. + :meth:`cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.facet`. Examples: - Build a 3-D stack from a 3-D variable's single time dim @@ -988,7 +988,7 @@ def _render_animate( call. Returns: - cleopatra.array_glyph.ArrayGlyph: The cleopatra glyph + cleopatra.glyphs.gridded.array_glyph.ArrayGlyph: The cleopatra glyph wrapping the streamed ``FuncAnimation``. The matplotlib animation object is reachable via the glyph's matplotlib figure. diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index eaa236349a..9f7db17822 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -71,7 +71,7 @@ from pyramids.netcdf.utils import _read_attributes, create_time_conversion_func if TYPE_CHECKING: - from cleopatra.geo import Basemap + from cleopatra.basemap.geo import Basemap # Guards the per-container `_lazy_managers` WeakSet against a concurrent lazy `read_array` (which adds) # and `close()` (which snapshots) on the same container from different threads. @@ -1774,14 +1774,14 @@ def plot( # type: ignore[override] passing the CRS the data was plotted in (its own CRS — no reprojection needed) so the layer lines up:: - from cleopatra.reference import add_features + from cleopatra.basemap.reference import add_features var = nc.get_variable("t2m") glyph = var.plot() add_features(glyph.ax, "coastline", crs=var.epsg, zorder=5) ``add_features`` fetches Natural Earth data (cached under ``~/.cleopatra``), so it needs the ``[viz]`` extra and network access on first use. A relief backdrop is - available the same way via :func:`cleopatra.reference.add_relief`. + available the same way via :func:`cleopatra.basemap.reference.add_relief`. Raises: TypeError: If any of the Sentinel-only kwargs (``rgb``, @@ -2009,7 +2009,7 @@ def plot( # type: ignore[override] - Facet over the time dim. :class:`FacetSpec` lists the column dim (and optionally a row dim and a wrap value). The return type becomes - :class:`cleopatra.array_glyph.FacetGrid`: + :class:`cleopatra.glyphs.gridded.array_glyph.FacetGrid`: ```python >>> from pyramids.netcdf import FacetSpec diff --git a/src/pyramids/netcdf/plot_options.py b/src/pyramids/netcdf/plot_options.py index 2932288901..d2914936b8 100644 --- a/src/pyramids/netcdf/plot_options.py +++ b/src/pyramids/netcdf/plot_options.py @@ -7,10 +7,10 @@ variable down to a single 2-D slice (or to the residual stack the facet / animate paths walk). - :class:`ColorOpts` — xarray-aligned colour controls forwarded - verbatim to cleopatra's :class:`~cleopatra.array_glyph.ArrayGlyph` + verbatim to cleopatra's :class:`~cleopatra.glyphs.gridded.array_glyph.ArrayGlyph` constructor. - :class:`FacetSpec` — multi-panel facet layout description forwarded - to :meth:`cleopatra.array_glyph.ArrayGlyph.facet`. + to :meth:`cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.facet`. Each dataclass is :func:`~dataclasses.dataclass` with ``frozen=True`` so callers cannot mutate the option bag once it has been handed to @@ -121,7 +121,7 @@ class ColorOpts: Mirrors the kwargs xarray's plotting accessor accepts. All fields are optional. Non-``None`` values are forwarded verbatim to - cleopatra's :class:`~cleopatra.array_glyph.ArrayGlyph`; the + cleopatra's :class:`~cleopatra.glyphs.gridded.array_glyph.ArrayGlyph`; the ``add_colorbar`` switch is applied post-render on the pyramids side because cleopatra does not accept the kwarg today. @@ -144,9 +144,9 @@ class ColorOpts: cbar_kwargs: Extra dict forwarded to :meth:`Figure.colorbar`. Defaults to None. style: Name of a cleopatra data-style preset (a key of - ``cleopatra.array_glyph.DATA_STYLES`` — e.g. ``"flow_accumulation"``, + ``cleopatra.glyphs.gridded.array_glyph.DATA_STYLES`` — e.g. ``"flow_accumulation"``, ``"topography"``) to colour the variable by. Forwarded to - :class:`~cleopatra.array_glyph.ArrayGlyph`; requires + :class:`~cleopatra.glyphs.gridded.array_glyph.ArrayGlyph`; requires cleopatra >= 0.24. Defaults to None (no preset). The rendered glyph that ``plot`` returns exposes ``glyph.apply_style(style)`` (cleopatra >= 0.25) to re-apply a preset by name in place without re-plotting. @@ -282,7 +282,7 @@ class FacetSpec: When set, ``NetCDF.plot`` builds a stack of slices along the named dims and hands them to - :meth:`cleopatra.array_glyph.ArrayGlyph.facet`. At least one of + :meth:`cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.facet`. At least one of ``col`` or ``row`` must be set; ``row`` alone (without ``col``) is invalid and rejected by the validator. diff --git a/src/pyramids/netcdf/ugrid/dataset.py b/src/pyramids/netcdf/ugrid/dataset.py index 6cd7ae40a5..4cfe109480 100644 --- a/src/pyramids/netcdf/ugrid/dataset.py +++ b/src/pyramids/netcdf/ugrid/dataset.py @@ -851,7 +851,7 @@ def plot( shaded-relief array. Returns: - cleopatra.mesh_glyph.MeshGlyph instance with the plot + cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph instance with the plot rendered. Use the returned object to access the matplotlib handles and the mappable: @@ -899,7 +899,7 @@ def plot_outline(self, ax: Any = None, **kwargs: Any) -> Any: **kwargs: Additional arguments passed to plot_mesh_outline. Returns: - cleopatra.mesh_glyph.MeshGlyph instance with the wireframe + cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph instance with the wireframe rendered. ``glyph.fig`` / ``glyph.ax`` are the matplotlib handles; ``glyph.im`` is ``None`` (an outline carries no scalar mapping, so no mappable is produced). diff --git a/src/pyramids/netcdf/ugrid/plot.py b/src/pyramids/netcdf/ugrid/plot.py index 53f5cb16cd..65b2c811fe 100644 --- a/src/pyramids/netcdf/ugrid/plot.py +++ b/src/pyramids/netcdf/ugrid/plot.py @@ -1,6 +1,6 @@ -"""UGRID mesh visualization (delegates to cleopatra.mesh_glyph). +"""UGRID mesh visualization (delegates to cleopatra.glyphs.gridded.mesh_glyph). -Thin wrapper around `cleopatra.mesh_glyph.MeshGlyph` that accepts +Thin wrapper around `cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph` that accepts pyramids `Mesh2d` objects and delegates all rendering to cleopatra. Requires the `viz` optional extra. Install with one of: @@ -10,11 +10,14 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any from pyramids.base._utils import require_cleopatra from pyramids.netcdf.ugrid.mesh import Mesh2d +if TYPE_CHECKING: + from cleopatra.styling.colorbar import ColorBar + _CLEOPATRA_MSG = ( "Mesh plotting requires the cleopatra package. Install with one of:\n" " - PyPI: pip install 'pyramids-gis[viz]'\n" @@ -40,7 +43,7 @@ def _mesh_to_glyph(mesh: Mesh2d, **kwargs: Any) -> Any: and any key in `MeshGlyph.default_options`. Returns: - cleopatra.mesh_glyph.MeshGlyph: A MeshGlyph instance ready + cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph: A MeshGlyph instance ready for plotting. Call `.plot()` or `.plot_outline()` on the returned object. @@ -49,7 +52,7 @@ def _mesh_to_glyph(mesh: Mesh2d, **kwargs: Any) -> Any: The error message includes install instructions. """ require_cleopatra(_CLEOPATRA_MSG) - from cleopatra.mesh_glyph import MeshGlyph + from cleopatra.glyphs.gridded.mesh_glyph import MeshGlyph edge_nodes = None if mesh.edge_node_connectivity is not None: @@ -75,7 +78,7 @@ def plot_mesh_data( vmin: float | None = None, vmax: float | None = None, edgecolor: str = "none", - colorbar: bool = True, + colorbar: bool | ColorBar | None = True, title: str | None = None, **kwargs: Any, ) -> Any: @@ -85,7 +88,7 @@ def plot_mesh_data( face-centered data, uses `tripcolor` (each triangle colored by the value of its parent face). For node-centered data, uses `tricontourf` (smooth interpolated contours). All rendering - is delegated to `cleopatra.mesh_glyph.MeshGlyph.plot`. + is delegated to `cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph.plot`. The returned `MeshGlyph` instance gives access to the underlying `Figure`/`Axes` and all cleopatra capabilities @@ -111,8 +114,10 @@ def plot_mesh_data( edgecolor: Edge color for face rendering. Use `"none"` for no edges or `"gray"` for visible mesh lines. Defaults to `"none"`. - colorbar: Whether to add a colorbar to the plot. Defaults - to True. + colorbar: Colour-bar control. `True` (default) draws the default bar, + `False`/`None` hides it, or pass a `pyramids.plot.ColorBar(...)` to + style it (caption / placement / sizing) — cleopatra >= 0.29 renders + the typed spec on `MeshGlyph` just like `ArrayGlyph`. title: Plot title string. Defaults to None (no title). **kwargs: Additional keyword arguments forwarded to `MeshGlyph.plot`. Common options include @@ -126,7 +131,7 @@ def plot_mesh_data( `location="node"`). Returns: - cleopatra.mesh_glyph.MeshGlyph: The MeshGlyph instance with + cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph: The MeshGlyph instance with the plot rendered. Access `glyph.fig` and `glyph.ax` for the matplotlib Figure and Axes. @@ -188,7 +193,7 @@ def plot_mesh_outline( `matplotlib.collections.LineCollection`. Returns: - cleopatra.mesh_glyph.MeshGlyph: The MeshGlyph instance with + cleopatra.glyphs.gridded.mesh_glyph.MeshGlyph: The MeshGlyph instance with the wireframe rendered. Access `glyph.fig` and `glyph.ax` for the matplotlib Figure and Axes. diff --git a/src/pyramids/plot.py b/src/pyramids/plot.py index a6d9b841b5..4198d3bec7 100644 --- a/src/pyramids/plot.py +++ b/src/pyramids/plot.py @@ -1,7 +1,7 @@ """Typed cleopatra plot specs, re-exported for pyramids. pyramids' ``plot`` and ``animate`` methods accept and forward the cleopatra -plotting spec objects (the ``[viz]`` extra requires cleopatra >= 0.28, which +plotting spec objects (the ``[viz]`` extra requires cleopatra >= 0.29, which carries the complete spec API). Import them from here so a call site never has to reach into ``cleopatra`` directly: @@ -37,18 +37,19 @@ if TYPE_CHECKING: # names for static type checkers / IDEs; resolved lazily at runtime # Re-exported for typing only (runtime resolution is via ``__getattr__``), so # ruff's unused-import rule is silenced rather than deleting the typing intent. - from cleopatra.array_glyph import ( # noqa: F401 - ColorBar, + from cleopatra.basemap.geo import Basemap, Feature # noqa: F401 + from cleopatra.glyphs.gridded.array_glyph import ( # noqa: F401 FrameLabel, PointOverlay, ) - from cleopatra.geo import Basemap, Feature # noqa: F401 + from cleopatra.styling.colorbar import ColorBar # noqa: F401 -_ARRAY_GLYPH = "cleopatra.array_glyph" -_GEO = "cleopatra.geo" +_ARRAY_GLYPH = "cleopatra.glyphs.gridded.array_glyph" +_COLORBAR = "cleopatra.styling.colorbar" +_GEO = "cleopatra.basemap.geo" _CLEO_EXPORTS: dict[str, tuple[str, str]] = { - "ColorBar": (_ARRAY_GLYPH, "ColorBar"), + "ColorBar": (_COLORBAR, "ColorBar"), "FrameLabel": (_ARRAY_GLYPH, "FrameLabel"), "PointOverlay": (_ARRAY_GLYPH, "PointOverlay"), "Basemap": (_GEO, "Basemap"), @@ -77,7 +78,7 @@ def __getattr__(name: str) -> Any: # branded upgrade hint instead of a bare AttributeError. raise OptionalPackageDoesNotExist( f"cleopatra is installed but too old to provide `{attribute}`. The " - "pyramids plotting specs require cleopatra >= 0.28; upgrade with: " + "pyramids plotting specs require cleopatra >= 0.29; upgrade with: " "pip install -U 'pyramids-gis[viz]'." ) from exc return spec diff --git a/tests/base/test_dtype_conversions.py b/tests/base/test_dtype_conversions.py index 40b98fccf8..e987a4487a 100644 --- a/tests/base/test_dtype_conversions.py +++ b/tests/base/test_dtype_conversions.py @@ -447,7 +447,7 @@ def test_returns_the_submodule_for_a_dotted_name(self): Test scenario: `__import__("a.b")` returns `a`, so the helper reads `sys.modules[name]` back instead. `import_basemap` passes - `cleopatra.tiles`, and only this path distinguishes the two. + `cleopatra.basemap.tiles`, and only this path distinguishes the two. """ module = require_optional("numpy.linalg", "unused", return_module=True) assert module.__name__ == "numpy.linalg", ( diff --git a/tests/basemap/test_basemap.py b/tests/basemap/test_basemap.py index acc288cbb4..9300f825cc 100644 --- a/tests/basemap/test_basemap.py +++ b/tests/basemap/test_basemap.py @@ -1,7 +1,7 @@ """Tests for pyramids.basemap.basemap. ``add_basemap`` and ``get_provider`` are thin wrappers over -``cleopatra.tiles.add_tiles`` / ``cleopatra.tiles.get_provider`` (the +``cleopatra.basemap.tiles.add_tiles`` / ``cleopatra.basemap.tiles.get_provider`` (the cleopatra C-6 helpers). These tests cover the delegation contract: the right cleopatra function is called with the translated kwargs, its return value is propagated, the missing-extra error path is wired up, and the @@ -17,29 +17,31 @@ pytestmark = pytest.mark.plot -pytest.importorskip("cleopatra.tiles", reason="cleopatra[tiles] extra not installed") +pytest.importorskip( + "cleopatra.basemap.tiles", reason="cleopatra[tiles] extra not installed" +) from pyramids.base._errors import OptionalPackageDoesNotExist from pyramids.basemap.basemap import add_basemap, get_provider class TestGetProvider: - """``get_provider`` delegates to ``cleopatra.tiles.get_provider``.""" + """``get_provider`` delegates to ``cleopatra.basemap.tiles.get_provider``.""" def test_delegates_to_cleopatra_get_provider(self): """The ``name`` argument is forwarded and the result returned verbatim. Test scenario: - Patch ``cleopatra.tiles.get_provider`` with a sentinel and + Patch ``cleopatra.basemap.tiles.get_provider`` with a sentinel and confirm ``get_provider("CartoDB.Positron")`` calls it once with that name and hands back its return value. """ - with patch("cleopatra.tiles.get_provider") as mock_get: + with patch("cleopatra.basemap.tiles.get_provider") as mock_get: result = get_provider("CartoDB.Positron") mock_get.assert_called_once_with("CartoDB.Positron") assert result is mock_get.return_value, ( - "get_provider must return cleopatra.tiles.get_provider's result" + "get_provider must return cleopatra.basemap.tiles.get_provider's result" ) def test_default_provider_round_trip(self): @@ -73,19 +75,19 @@ def test_missing_extra_raises(self): class TestAddBasemap: - """``add_basemap`` delegates to ``cleopatra.tiles.add_tiles``.""" + """``add_basemap`` delegates to ``cleopatra.basemap.tiles.add_tiles``.""" def test_delegates_with_default_kwargs(self): """A bare ``add_basemap(ax)`` forwards the documented defaults. Test scenario: - Patch ``cleopatra.tiles.add_tiles`` and call ``add_basemap`` + Patch ``cleopatra.basemap.tiles.add_tiles`` and call ``add_basemap`` with only ``ax``. Every wrapper-level default must reach cleopatra as a keyword (``crs=3857``, ``zoom="auto"``, …) and the axes must be passed positionally. """ sentinel_ax = object() - with patch("cleopatra.tiles.add_tiles") as mock_add: + with patch("cleopatra.basemap.tiles.add_tiles") as mock_add: result = add_basemap(sentinel_ax) mock_add.assert_called_once_with( @@ -101,7 +103,7 @@ def test_delegates_with_default_kwargs(self): retries=2, ) assert result is mock_add.return_value, ( - "add_basemap must return cleopatra.tiles.add_tiles's result" + "add_basemap must return cleopatra.basemap.tiles.add_tiles's result" ) def test_delegates_with_custom_kwargs(self): @@ -113,7 +115,7 @@ def test_delegates_with_custom_kwargs(self): those values (and the untouched defaults for the rest). """ sentinel_ax = object() - with patch("cleopatra.tiles.add_tiles") as mock_add: + with patch("cleopatra.basemap.tiles.add_tiles") as mock_add: add_basemap( sentinel_ax, crs=4326, @@ -141,22 +143,22 @@ def test_missing_extra_raises(self): Test scenario: Patch the ``import_basemap`` guard to raise; ``add_basemap`` must propagate ``OptionalPackageDoesNotExist`` and never reach - ``cleopatra.tiles.add_tiles``. + ``cleopatra.basemap.tiles.add_tiles``. """ with patch( "pyramids.basemap.basemap.import_basemap", side_effect=OptionalPackageDoesNotExist("no cleopatra[tiles]"), ): - with patch("cleopatra.tiles.add_tiles") as mock_add: + with patch("cleopatra.basemap.tiles.add_tiles") as mock_add: with pytest.raises(OptionalPackageDoesNotExist): add_basemap(object()) mock_add.assert_not_called() class TestCleopatraDelegation: - """The ``add_basemap`` -> ``cleopatra.tiles.add_tiles`` contract. + """The ``add_basemap`` -> ``cleopatra.basemap.tiles.add_tiles`` contract. - ``add_basemap`` is a thin wrapper over ``cleopatra.tiles.add_tiles`` + ``add_basemap`` is a thin wrapper over ``cleopatra.basemap.tiles.add_tiles`` (shipped in ``cleopatra >= 0.8.0``, pinned via the ``[viz]`` extra as ``cleopatra[tiles]``). These tests pin the contract that wrapper relies on: the helper is importable, and the two signatures stay compatible — @@ -166,18 +168,18 @@ class TestCleopatraDelegation: """ def test_cleopatra_add_tiles_is_importable(self): - """``cleopatra.tiles.add_tiles`` imports (C-6 contract).""" + """``cleopatra.basemap.tiles.add_tiles`` imports (C-6 contract).""" cleopatra_tiles = pytest.importorskip( - "cleopatra.tiles", + "cleopatra.basemap.tiles", reason="cleopatra[tiles] extra not installed", ) assert hasattr(cleopatra_tiles, "add_tiles"), ( - "cleopatra.tiles.add_tiles must exist so " + "cleopatra.basemap.tiles.add_tiles must exist so " "pyramids.basemap.add_basemap can delegate to it." ) def test_pyramids_and_cleopatra_share_addmap_signature(self): - """``add_basemap`` and ``cleopatra.tiles.add_tiles`` line up. + """``add_basemap`` and ``cleopatra.basemap.tiles.add_tiles`` line up. Test scenario: Compare the parameter names directly so a future cleopatra @@ -185,7 +187,7 @@ def test_pyramids_and_cleopatra_share_addmap_signature(self): at a downstream call site. """ cleopatra_tiles = pytest.importorskip( - "cleopatra.tiles", + "cleopatra.basemap.tiles", reason="cleopatra[tiles] extra not installed", ) cleo_sig = inspect.signature(cleopatra_tiles.add_tiles) @@ -196,18 +198,18 @@ def test_pyramids_and_cleopatra_share_addmap_signature(self): extra = pyr_params - cleo_params # Cleopatra is allowed to grow its surface: new *optional* params # (e.g. `user_agent=` / `max_tiles=` in 0.8.0) don't break the - # `add_basemap(ax, ...) -> cleopatra.tiles.add_tiles(ax, ...)` + # `add_basemap(ax, ...) -> cleopatra.basemap.tiles.add_tiles(ax, ...)` # delegation — pyramids just won't expose them. What *would* break # it is cleopatra making a previously-shared (or any) param # *required* while pyramids lacks it. for name in missing: param = cleo_sig.parameters[name] assert param.default is not inspect.Parameter.empty, ( - f"cleopatra.tiles.add_tiles added a *required* param {name!r} " + f"cleopatra.basemap.tiles.add_tiles added a *required* param {name!r} " "not in pyramids.add_basemap — the delegation contract is broken." ) assert not extra, ( - f"pyramids.add_basemap has params not in cleopatra.tiles.add_tiles: " + f"pyramids.add_basemap has params not in cleopatra.basemap.tiles.add_tiles: " f"{extra}. The thin-wrapper delegation will not work until " "these are pushed upstream." ) diff --git a/tests/dataset/plot/test_basemap_dispatch.py b/tests/dataset/plot/test_basemap_dispatch.py index cc434f2473..950b98e1d6 100644 --- a/tests/dataset/plot/test_basemap_dispatch.py +++ b/tests/dataset/plot/test_basemap_dispatch.py @@ -1,7 +1,7 @@ """Tests for the `basemap=` type-dispatch in `render_array` (cleopatra 0.27). A string provider name (or `True`) draws pyramids' own web-tile basemap under -the raster; a `cleopatra.geo.Basemap` forwards to the glyph's relief/features +the raster; a `cleopatra.basemap.geo.Basemap` forwards to the glyph's relief/features reference layer instead. """ @@ -17,13 +17,15 @@ # Version-gate first: the module binds 0.28-only specs (Basemap) at module scope, # so an installed-but-older cleopatra must skip cleanly, not error at collection. -pytest.importorskip("cleopatra", minversion="0.28", reason="needs cleopatra >= 0.28") +pytest.importorskip("cleopatra", minversion="0.29", reason="needs cleopatra >= 0.29") _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") _cleo_config.Config.set_matplotlib_backend("agg") _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" +) +_cleo_geo = pytest.importorskip( + "cleopatra.basemap.geo", reason="cleopatra not installed" ) -_cleo_geo = pytest.importorskip("cleopatra.geo", reason="cleopatra not installed") ArrayGlyph = _cleo_array.ArrayGlyph Basemap = _cleo_geo.Basemap plt = pytest.importorskip("matplotlib.pyplot", reason="cleopatra not installed") diff --git a/tests/dataset/plot/test_new_render_params.py b/tests/dataset/plot/test_new_render_params.py index e5fda60cec..b1de04a0ef 100644 --- a/tests/dataset/plot/test_new_render_params.py +++ b/tests/dataset/plot/test_new_render_params.py @@ -17,11 +17,11 @@ # Version-gate first: the module binds a 0.28-only spec (ColorBar) at module scope, # so an installed-but-older cleopatra must skip cleanly, not error at collection. -pytest.importorskip("cleopatra", minversion="0.28", reason="needs cleopatra >= 0.28") +pytest.importorskip("cleopatra", minversion="0.29", reason="needs cleopatra >= 0.29") _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") _cleo_config.Config.set_matplotlib_backend("agg") _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph ColorBar = _cleo_array.ColorBar @@ -149,20 +149,22 @@ def test_colorbar_true_still_folds_loose_cbar(self): f"colorbar=True must keep the loose label, got {label!r}" ) - def test_deprecated_cbar_kwarg_renders_on_the_facet_path(self): - """A loose `cbar_*` kwarg still renders the shared colour-bar label on the facet path. + def test_deprecated_cbar_kwarg_folds_into_colorbar_on_the_facet_path(self): + """A loose `cbar_*` kwarg folds into a ColorBar and renders on the facet path. - cleopatra's `facet` does not accept `colorbar=ColorBar` (only the loose `cbar_*` - kwargs), so the translation is skipped on the facet path — the loose form is kept, - and the returned grid's shared colour bar still carries the label. + cleopatra >= 0.29 accepts `colorbar=ColorBar` on `ArrayGlyph.facet` + (serapeum-org/cleopatra#271), so the facet path folds the deprecated loose + `cbar_*` into a ColorBar (emitting a DeprecationWarning) like plot/animate; the + returned grid's shared colour bar still carries the label. """ stack = np.random.default_rng(3).random((3, 6, 6)).astype("float32") - result = render_array( - arr=stack, - mode="facet", - facet_kwargs={"col": "time", "col_coords": [0, 1, 2]}, - cbar_label="mm", - extent=[0.0, 0.0, 1.0, 1.0], - ) + with pytest.warns(DeprecationWarning, match="cbar_"): + result = render_array( + arr=stack, + mode="facet", + facet_kwargs={"col": "time", "col_coords": [0, 1, 2]}, + cbar_label="mm", + extent=[0.0, 0.0, 1.0, 1.0], + ) label = result.cbar.ax.get_ylabel() or result.cbar.ax.get_xlabel() assert label == "mm", f"facet colour-bar label should render, got {label!r}" diff --git a/tests/dataset/plot/test_plot_cleanups.py b/tests/dataset/plot/test_plot_cleanups.py index cd4c3de543..dbc5c96fb4 100644 --- a/tests/dataset/plot/test_plot_cleanups.py +++ b/tests/dataset/plot/test_plot_cleanups.py @@ -13,7 +13,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") @@ -332,7 +332,7 @@ def plot(self, **kwargs): return (None, self.ax) with _patch( - "cleopatra.array_glyph.ArrayGlyph", + "cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=_FakeGlyph, ): _rarr( diff --git a/tests/dataset/plot/test_plot_dataset.py b/tests/dataset/plot/test_plot_dataset.py index 454a631076..bd9d98bb33 100644 --- a/tests/dataset/plot/test_plot_dataset.py +++ b/tests/dataset/plot/test_plot_dataset.py @@ -13,7 +13,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") @@ -103,7 +103,7 @@ def test_plot_histogram_excludes_invalid_samples(self): Test scenario: A band carrying a no-data pixel (``-9999``) and a repeated ``7.0`` is histogrammed with ``exclude_value=7.0``. Capturing - the values handed to ``StatisticalGlyph`` proves both the + the values handed to ``HistogramGlyph`` proves both the no-data value and the explicit ``exclude_value`` are dropped, leaving only the genuine samples. """ @@ -128,7 +128,9 @@ def __init__(self, values, ax=None, **kwargs): def histogram(self, bins=15): return ("fig", "ax", {}) - with patch("cleopatra.statistical_glyph.StatisticalGlyph", new=_FakeSG): + with patch( + "cleopatra.glyphs.stats.histogram_glyph.HistogramGlyph", new=_FakeSG + ): dataset.plot_histogram(band=0, bins=5, exclude_value=7.0) vals = sorted(captured["values"].tolist()) assert vals == [ @@ -231,7 +233,7 @@ def test_plot_histogram_all_nodata_raises(self): Test scenario: Every pixel equals the no-data value, so after masking there are no samples; ``plot_histogram`` must raise a targeted ``ValueError`` - rather than passing an empty array to ``StatisticalGlyph``. + rather than passing an empty array to ``HistogramGlyph``. """ arr = np.full((4, 4), -9999.0, dtype="float32") dataset = Dataset.create_from_array( @@ -479,7 +481,7 @@ def test_geotiff( rasters_folder_rasters_number: int, rasters_folder_dim: tuple, ): - from cleopatra.array_glyph import ArrayGlyph + from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph cube = DatasetCollection.read_multiple_files( rasters_folder_path, with_order=False diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index bf069699f3..7873f4b09b 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -13,7 +13,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph # cleopatra >= 0.26 bundles the point-overlay styling kwargs into this class, @@ -158,7 +158,7 @@ def test_constructor_owns_cmap_vmin_vmax_levels_cbar(self): fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(101) arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], @@ -197,7 +197,7 @@ def test_render_call_only_kwargs_reach_plot(self): "full_bleed": True, "kind": "imshow", } - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], @@ -223,7 +223,7 @@ def test_animate_mode_merges_both_buckets_into_animate_call(self): fake_cls, ctor, _, animate, _, anim_args = self._capture_calls() rng = np.random.default_rng(303) arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], @@ -258,7 +258,7 @@ def test_facet_mode_routes_kind_to_facet_call(self): fake_cls, ctor, _, _, facet, _ = self._capture_calls() rng = np.random.default_rng(404) arr = rng.random((3, 4, 4)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], @@ -291,7 +291,7 @@ def test_split_is_driven_by_option_keys(self): fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(505) arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], @@ -318,7 +318,7 @@ def test_deprecated_cbar_kwargs_fold_into_colorbar(self): fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(506) arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): with pytest.warns(DeprecationWarning, match="cbar_"): render_array( arr=arr, @@ -353,7 +353,7 @@ def test_deprecated_point_kwargs_fold_into_pointoverlay(self): rng = np.random.default_rng(507) arr = rng.random((4, 4)).astype("float32") pts = np.array([[1.0, 2, 3]]) - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): with pytest.warns(DeprecationWarning, match="point_"): render_array( arr=arr, @@ -386,7 +386,7 @@ def test_deprecated_pid_kwargs_fold_into_pointoverlay_labels(self): rng = np.random.default_rng(508) arr = rng.random((4, 4)).astype("float32") pts = np.array([[1.0, 2, 3]]) - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): with pytest.warns(DeprecationWarning, match="pid_"): render_array( arr=arr, @@ -424,7 +424,7 @@ def test_point_label_alias_wins_over_legacy_pid(self): rng = np.random.default_rng(509) arr = rng.random((4, 4)).astype("float32") pts = np.array([[1.0, 2, 3]]) - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): with pytest.warns(DeprecationWarning, match="point_"): render_array( arr=arr, @@ -451,7 +451,7 @@ def test_loose_point_kwargs_ignored_when_points_is_pointoverlay(self): rng = np.random.default_rng(510) arr = rng.random((4, 4)).astype("float32") overlay_in = PointOverlay(np.array([[1.0, 2, 3]]), color="black") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): with pytest.warns(DeprecationWarning, match="ignored because points="): render_array( arr=arr, @@ -477,7 +477,7 @@ def test_loose_point_kwargs_dropped_when_no_points(self): fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(511) arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): with pytest.warns(DeprecationWarning, match="no effect"): render_array( arr=arr, @@ -640,7 +640,7 @@ def test_falsy_hillshade_is_dropped_not_forwarded(self, value): fake_cls, ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() rng = np.random.default_rng(745) arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade=value ) @@ -659,7 +659,7 @@ def test_none_style_is_dropped_not_forwarded(self): fake_cls, ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() rng = np.random.default_rng(748) arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array(arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", style=None) assert "style" not in ctor, "style=None must not reach the ctor" assert "style" not in plot, "style=None must not reach plot" @@ -675,7 +675,7 @@ def test_empty_dict_hillshade_is_forwarded(self): fake_cls, ctor, _plot, *_ = TestRenderArrayKwargRouting._capture_calls() rng = np.random.default_rng(746) arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade={} ) @@ -980,7 +980,7 @@ def test_point_overlay_reaches_the_render_call(self): overlay = PointOverlay(self._points(), color="red") rng = np.random.default_rng(761) arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.array_glyph.ArrayGlyph", new=fake_cls): + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", points=overlay ) diff --git a/tests/dataset/plot/test_plot_facade.py b/tests/dataset/plot/test_plot_facade.py index 19b1f4fddf..12697b3572 100644 --- a/tests/dataset/plot/test_plot_facade.py +++ b/tests/dataset/plot/test_plot_facade.py @@ -12,7 +12,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") @@ -42,7 +42,7 @@ def test_returns_array_glyph(self): Test scenario: Calling the facade with a single-band dataset must return - an instance of :class:`cleopatra.array_glyph.ArrayGlyph` so + an instance of :class:`cleopatra.glyphs.gridded.array_glyph.ArrayGlyph` so downstream callers can chain visual customisations. """ rng = np.random.default_rng(1337) @@ -91,7 +91,7 @@ def test_two_consecutive_calls_return_independent_figures(self): ], ) def test_color_scale_string_aliases_work(self, color_scale): - """``color_scale`` accepts the ``cleopatra.styles.ColorScale`` aliases. + """``color_scale`` accepts the ``cleopatra.styling.styles.ColorScale`` aliases. Args: color_scale: String alias for the colour-scale enum (lookup diff --git a/tests/dataset/plot/test_plot_specs.py b/tests/dataset/plot/test_plot_specs.py index 9d1b2a9dc9..773cacd2f5 100644 --- a/tests/dataset/plot/test_plot_specs.py +++ b/tests/dataset/plot/test_plot_specs.py @@ -26,11 +26,11 @@ def test_all_and_dir_list_the_specs(self): @pytest.mark.parametrize( "name, module_name, attribute", [ - ("ColorBar", "cleopatra.array_glyph", "ColorBar"), - ("FrameLabel", "cleopatra.array_glyph", "FrameLabel"), - ("PointOverlay", "cleopatra.array_glyph", "PointOverlay"), - ("Basemap", "cleopatra.geo", "Basemap"), - ("Feature", "cleopatra.geo", "Feature"), + ("ColorBar", "cleopatra.styling.colorbar", "ColorBar"), + ("FrameLabel", "cleopatra.glyphs.gridded.array_glyph", "FrameLabel"), + ("PointOverlay", "cleopatra.glyphs.gridded.array_glyph", "PointOverlay"), + ("Basemap", "cleopatra.basemap.geo", "Basemap"), + ("Feature", "cleopatra.basemap.geo", "Feature"), ], ) def test_spec_resolves_to_cleopatra_class(self, name, module_name, attribute): @@ -66,5 +66,5 @@ class _OldCleopatraModule: with patch( "pyramids.plot.require_optional", return_value=_OldCleopatraModule() ): - with pytest.raises(OptionalPackageDoesNotExist, match="0.28"): + with pytest.raises(OptionalPackageDoesNotExist, match="0.29"): plot_specs.ColorBar diff --git a/tests/feature/test_plot_cleopatra.py b/tests/feature/test_plot_cleopatra.py index b975ad4a5a..eaafe9012a 100644 --- a/tests/feature/test_plot_cleopatra.py +++ b/tests/feature/test_plot_cleopatra.py @@ -20,8 +20,12 @@ # Guard every optional-dependency import (cleopatra + its matplotlib backend) # behind importorskip so the module skips cleanly in a core-only install # (e.g. the wheel-test job), rather than erroring at collection. -_pg = pytest.importorskip("cleopatra.polygon_glyph", reason="cleopatra not installed") -_sg = pytest.importorskip("cleopatra.scatter_glyph", reason="cleopatra not installed") +_pg = pytest.importorskip( + "cleopatra.glyphs.primitives.polygon_glyph", reason="cleopatra not installed" +) +_sg = pytest.importorskip( + "cleopatra.glyphs.primitives.scatter_glyph", reason="cleopatra not installed" +) _cfg = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") _mpl_axes = pytest.importorskip("matplotlib.axes", reason="matplotlib not installed") _cfg.Config.set_matplotlib_backend("agg") diff --git a/tests/netcdf/plot/test_plot_animate.py b/tests/netcdf/plot/test_plot_animate.py index b19626afd5..39fe36e5dc 100644 --- a/tests/netcdf/plot/test_plot_animate.py +++ b/tests/netcdf/plot/test_plot_animate.py @@ -16,7 +16,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph # cleopatra >= 0.26 bundles the animate frame-label pair into FrameLabel. diff --git a/tests/netcdf/plot/test_plot_coords.py b/tests/netcdf/plot/test_plot_coords.py index a07c62cda3..731d07ee0c 100644 --- a/tests/netcdf/plot/test_plot_coords.py +++ b/tests/netcdf/plot/test_plot_coords.py @@ -23,7 +23,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") @@ -360,7 +360,7 @@ def test_kind_bogus_raises_value_error(self): Test scenario: cleopatra validates ``kind`` against - :data:`cleopatra.array_glyph.VALID_PLOT_KINDS`. An unknown + :data:`cleopatra.glyphs.gridded.array_glyph.VALID_PLOT_KINDS`. An unknown value triggers a ValueError that must propagate through pyramids unchanged so users see the same error message they would see calling ArrayGlyph directly. diff --git a/tests/netcdf/plot/test_plot_facet.py b/tests/netcdf/plot/test_plot_facet.py index 3ad5898df4..3ce2c1aedd 100644 --- a/tests/netcdf/plot/test_plot_facet.py +++ b/tests/netcdf/plot/test_plot_facet.py @@ -15,7 +15,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) _FacetGrid = _cleo_array.FacetGrid _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") @@ -28,7 +28,7 @@ class TestNetCDFPlotFaceting: Each test exercises the cleopatra `ArrayGlyph.facet` wiring on NetCDF.plot. The returned object is a - :class:`cleopatra.array_glyph.FacetGrid`; subplot count and the + :class:`cleopatra.glyphs.gridded.array_glyph.FacetGrid`; subplot count and the `name_dicts` index map confirm the dispatch. """ diff --git a/tests/netcdf/plot/test_plot_lazy.py b/tests/netcdf/plot/test_plot_lazy.py index 7f65ef7acc..b189aae40c 100644 --- a/tests/netcdf/plot/test_plot_lazy.py +++ b/tests/netcdf/plot/test_plot_lazy.py @@ -16,7 +16,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") diff --git a/tests/netcdf/plot/test_plot_render.py b/tests/netcdf/plot/test_plot_render.py index 4b0e6cfe88..7bb5e7f8fc 100644 --- a/tests/netcdf/plot/test_plot_render.py +++ b/tests/netcdf/plot/test_plot_render.py @@ -18,7 +18,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") diff --git a/tests/netcdf/plot/test_plot_selectors.py b/tests/netcdf/plot/test_plot_selectors.py index 3c4a162197..5073bf40e0 100644 --- a/tests/netcdf/plot/test_plot_selectors.py +++ b/tests/netcdf/plot/test_plot_selectors.py @@ -20,7 +20,7 @@ pytestmark = pytest.mark.plot _cleo_array = pytest.importorskip( - "cleopatra.array_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" ) ArrayGlyph = _cleo_array.ArrayGlyph _cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index 3553a2d8fc..c43f3c693a 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -12,7 +12,7 @@ import pytest mesh_glyph = pytest.importorskip( - "cleopatra.mesh_glyph", reason="cleopatra not installed" + "cleopatra.glyphs.gridded.mesh_glyph", reason="cleopatra not installed" ) MeshGlyph = mesh_glyph.MeshGlyph from pyramids.base._errors import OptionalPackageDoesNotExist @@ -64,6 +64,25 @@ def test_plot_sets_mappable_im(self, triangle_mesh): result = plot_mesh_data(triangle_mesh, data, location="face") assert result.im is not None, "plot() must set the mesh mappable on .im" + def test_typed_colorbar_renders_on_mesh(self, triangle_mesh): + """A `ColorBar` spec styles the mesh colour bar (cleopatra >= 0.29, #933). + + Test scenario: + cleopatra 0.29 accepts `colorbar=ColorBar` on `MeshGlyph.plot`, so passing + a typed spec draws a labelled colour bar rather than only accepting `bool`. + """ + from cleopatra.styling.colorbar import ColorBar + + data = np.array([1.0, 2.0]) + result = plot_mesh_data( + triangle_mesh, data, location="face", colorbar=ColorBar(label="depth") + ) + assert result._cbar is not None, "a ColorBar spec must draw a colour bar" + label = result._cbar.ax.get_ylabel() or result._cbar.ax.get_xlabel() + assert label == "depth", ( + f"mesh colour bar should carry the label, got {label!r}" + ) + def test_invalid_location_raises(self, triangle_mesh): """Test that invalid location raises ValueError.""" data = np.array([1.0]) From 75cf4cc6b827b82cf0871a525deac9bf76e28965 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Fri, 7 Aug 2026 23:34:18 +0200 Subject: [PATCH 03/30] build(deps): note the cleopatra restructure ships in 0.30.0, keep the git source PyPI cleopatra 0.29.0 is the pre-restructure flat layout; the subpackage restructure lands in 0.30.0. Keep installing from git main (self-reports 0.29.0, carries the new layout) and bump the [viz] floor to >=0.30.0 (dropping the git source) at that release. --- pyproject.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3c462caebe..15e0a0b46c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -296,11 +296,11 @@ platforms = ["win-64", "linux-64", "linux-aarch64", "osx-arm64", "osx-64"] [tool.pixi.pypi-dependencies] pyramids-gis = { path = ".", editable = true } -# Dev/CI track cleopatra's restructured main (0.29) ahead of its PyPI/conda-forge -# release; git main (0.29.0) satisfies the published [viz] extra's `>=0.28.0`, so the -# git source wins in every dev/test/docs env while the built wheel keeps a plain -# version spec (PyPI rejects direct-URL metadata). Remove this and bump the [viz] -# floor to `>=0.29.0` once cleopatra 0.29 is released. +# cleopatra's module restructure lands in its 0.30.0 release; until then pyramids +# needs it from git main (which carries the new subpackage layout but still +# self-reports 0.29.0). Dev/CI track main here; the published [viz] extra stays a +# plain version spec so built wheels carry no direct-URL metadata (PyPI rejects it). +# Remove this and bump the [viz] floor to `>=0.30.0` once cleopatra 0.30.0 releases. cleopatra = { git = "https://github.com/serapeum-org/cleopatra.git", branch = "main", extras = ["tiles"] } [tool.pixi.tasks] From bcd1f0d1c07ee3733848d2ecf78b53642706bdf3 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Sat, 8 Aug 2026 00:40:38 +0200 Subject: [PATCH 04/30] refactor(plot): align .plot/.animate kwargs with cleopatra's typed API Mirror cleopatra 0.29's typed plot surface on the pyramids facades so the render controls are discoverable on the signature instead of buried in an untyped `**kwargs: Any`: - Dataset.plot: hoist `colorbar` / `points` / `kind` / `title` to explicit params and type the tail as `**kwargs: Unpack[PlotKwargs]`. - DatasetCollection.plot: hoist `colorbar` / `points` and the pyramids-only `animation_axis_values` override to explicit params; type the tail as `**kwargs: Unpack[AnimateKwargs]` (`title` already lives in AnimateKwargs). - NetCDF.plot: hoist `colorbar` / `points` for parity. The method is a multi-mode facade (static / animate / facet) whose valid kwargs differ per mode, so its tail stays `**kwargs: Any`; the hoisted params are only forwarded when set so a default never reaches the facet path (which rejects `points`). cleopatra ships inline types but no `py.typed`, so add a mypy override that follows its source types (else `Unpack[PlotKwargs]` collapses to `Unpack[Any]`), plus a narrow `arg-type` suppression on the single dynamic-dispatch backend whose runtime-built `**dict` cannot be statically matched to the now-typed cleopatra signatures. --- pyproject.toml | 18 ++++++++++++++++++ src/pyramids/dataset/_plot_helpers.py | 3 ++- src/pyramids/dataset/collection.py | 23 +++++++++++++++++++---- src/pyramids/dataset/dataset.py | 16 +++++++++++++--- src/pyramids/netcdf/netcdf.py | 13 +++++++++++++ 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 15e0a0b46c..b1224ca581 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -234,6 +234,24 @@ exclude = [ "planning/", ] +[[tool.mypy.overrides]] +# cleopatra ships inline types (the TypedDicts PlotKwargs/AnimateKwargs, the ColorBar / +# PointOverlay / Basemap dataclasses, ...) but no `py.typed` marker yet, so the global +# `ignore_missing_imports` would collapse them to `Any` and break `Unpack[PlotKwargs]`. +# Follow its source types instead. Drop once cleopatra ships `py.typed`. +module = "cleopatra.*" +ignore_missing_imports = false +follow_untyped_imports = true + +[[tool.mypy.overrides]] +# render_array is the single dynamic-dispatch backend: it splits **kwargs into ctor / +# render buckets at runtime (keyed on ArrayGlyph.option_keys()) and splats them into +# cleopatra's now-typed plot/animate/facet. mypy cannot verify a runtime-built **dict +# against a typed signature, so `arg-type` here is a wall of false positives (the 1367 +# plot tests prove the routing). Suppress just that code for this one module. +module = "pyramids.dataset._plot_helpers" +disable_error_code = ["arg-type", "no-any-return"] + [[tool.mypy.overrides]] module = "pyramids.dataset.ops.*" disable_error_code = [ diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index 121f9cfa90..8e0140e25e 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -79,6 +79,7 @@ if TYPE_CHECKING: from cleopatra.basemap.geo import Basemap + from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph # N-6 — Mesh rendering shares this module's "data in, glyph out" # contract via :func:`mesh_render`. The function lives next to @@ -335,7 +336,7 @@ def render_array( basemap: bool | str | dict[str, Any] | Basemap | None = None, basemap_epsg: int | None = None, **kwargs: Any, -): +) -> ArrayGlyph: """Build an ArrayGlyph and dispatch to the right cleopatra render path. Args: diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index b51f6d5f84..7d74e352d4 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -8,7 +8,7 @@ import warnings from collections.abc import Callable, Sequence from pathlib import Path -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Unpack, cast import numpy as np import pandas as pd @@ -47,7 +47,13 @@ if TYPE_CHECKING: from cleopatra.basemap.geo import Basemap - from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, FrameLabel + from cleopatra.glyphs.gridded.array_glyph import ( + AnimateKwargs, + ArrayGlyph, + FrameLabel, + PointOverlay, + ) + from cleopatra.styling.colorbar import ColorBar from dask.delayed import Delayed @@ -2428,7 +2434,10 @@ def plot( rgb_options: dict | None = None, basemap: bool | str | dict[str, Any] | Basemap | None = None, frame_label: FrameLabel | None = None, - **kwargs: Any, + colorbar: bool | ColorBar | None = None, + points: np.ndarray | PointOverlay | None = None, + animation_axis_values: Any = None, + **kwargs: Unpack[AnimateKwargs], ) -> ArrayGlyph: r"""Render the collection as an animated stack of band slices. @@ -2608,7 +2617,11 @@ def plot( default_labels = ( list(self.time) if self.time is not None else list(range(self.time_length)) ) - axis_values = kwargs.pop("animation_axis_values", default_labels) + axis_values = ( + default_labels + if animation_axis_values is None + else animation_axis_values + ) if not hasattr(axis_values, "__len__"): axis_values = list(axis_values) # materialise a generator override # An explicit override must carry exactly one label per frame; the @@ -2628,6 +2641,8 @@ def plot( animate_extras: dict[str, Any] = { "basemap": basemap, "basemap_epsg": self.base.epsg, + "colorbar": colorbar, + "points": points, } if frame_label is not None: animate_extras["frame_label"] = frame_label diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index bcc050f75a..ac2f3c5d17 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -13,7 +13,7 @@ from collections.abc import Sequence from numbers import Number from pathlib import Path -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any, Unpack, cast import geopandas as gpd import numpy as np @@ -233,6 +233,8 @@ def _remap_nodata_to(arr: np.ndarray, src_nd: Any, dst_nd: Any) -> np.typing.NDA if TYPE_CHECKING: import dask.array as da from cleopatra.basemap.geo import Basemap + from cleopatra.glyphs.gridded.array_glyph import PlotKwargs, PointOverlay + from cleopatra.styling.colorbar import ColorBar from geopandas import GeoDataFrame @@ -798,7 +800,7 @@ def _infer_rgb_band_order(self) -> list[int]: resolved_rgb = [int(v) for v in cast("list[int]", candidate)] return resolved_rgb - def plot( + def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed PlotKwargs self, band: int | None = None, exclude_value: Any | None = None, @@ -809,8 +811,12 @@ def plot( overview_index: int | None = 0, percentile: int | None = None, basemap: bool | str | dict[str, Any] | Basemap | None = None, + colorbar: bool | ColorBar | None = None, + points: np.ndarray | PointOverlay | None = None, + kind: str = "auto", + title: str | None = None, rgb_options: dict | None = None, - **kwargs: Any, + **kwargs: Unpack[PlotKwargs], ): """Plot the values/overviews of a band. @@ -970,6 +976,10 @@ def plot( overview_index=overview_index, percentile=percentile, basemap=basemap, + colorbar=colorbar, + points=points, + kind=kind, + title=title, **kwargs, ) diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index 9f7db17822..79c6321fd6 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -72,6 +72,8 @@ if TYPE_CHECKING: from cleopatra.basemap.geo import Basemap + from cleopatra.glyphs.gridded.array_glyph import PointOverlay + from cleopatra.styling.colorbar import ColorBar # Guards the per-container `_lazy_managers` WeakSet against a concurrent lazy `read_array` (which adds) # and `close()` (which snapshots) on the same container from different threads. @@ -1650,6 +1652,8 @@ def plot( # type: ignore[override] title: str | None = None, ax: Any | None = None, figsize: tuple[float, float] | None = None, + colorbar: bool | ColorBar | None = None, + points: np.ndarray | PointOverlay | None = None, **kwargs: Any, ): """Plot a 2-D slice of a NetCDF variable using xarray-aligned vocabulary. @@ -2116,6 +2120,15 @@ def plot( # type: ignore[override] ``` """ + # ``colorbar``/``points`` are hoisted to explicit params for parity with + # ``Dataset.plot`` / ``DatasetCollection.plot``; forward them through the + # same ``**kwargs`` channel the multi-mode render path already consumes. + # Only inject when set — the facet path rejects ``points`` outright, so a + # default ``None`` must not reach it. + if colorbar is not None: + kwargs["colorbar"] = colorbar + if points is not None: + kwargs["points"] = points return NetCDFPlot(self).run( variable, selectors=selectors, From cf9a7c343a79404f86c23e6f3557173140988ea2 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Sat, 8 Aug 2026 00:54:58 +0200 Subject: [PATCH 05/30] test(plot): cover NetCDF hoisted colorbar/points; document hoisted plot params MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to the .plot/.animate kwargs alignment: - Add tests/netcdf/plot/test_hoisted_render_params.py covering the genuinely new NetCDF surface: the hoisted `colorbar` / `points` render on the static path, the conditional-injection contract (a default `None` is never forwarded), and the facet edge — a bare facet renders while an explicit `points` on the facet path raises a clear error. (Dataset `points` and Collection `animation_axis_values` were already covered.) - Document the hoisted params as explicit `Args` on all three facades: `colorbar` / `points` / `kind` / `title` on Dataset.plot; `colorbar` / `points` / `animation_axis_values` moved out of the DatasetCollection.plot `**kwargs` table into Args (beside the already-explicit `frame_label`); `colorbar` / `points` on NetCDF.plot with the facet caveat noted. --- src/pyramids/dataset/collection.py | 23 ++- src/pyramids/dataset/dataset.py | 15 ++ src/pyramids/netcdf/netcdf.py | 11 ++ .../netcdf/plot/test_hoisted_render_params.py | 174 ++++++++++++++++++ 4 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 tests/netcdf/plot/test_hoisted_render_params.py diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index 7d74e352d4..f9cec8c93b 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2517,15 +2517,32 @@ def plot( (colour, size, placement). ``animation_axis_values`` sets the label *text* per frame; ``frame_label`` styles it. Default ``None`` (cleopatra's default frame label). + colorbar (bool or ColorBar, optional): + Colour-bar spec ``pyramids.plot.ColorBar(label=…, length=…, + orientation=…, label_size=…, label_rotation=…, label_location=…, + ticks_spacing=…)`` (cleopatra >= 0.28). The loose ``cbar_*`` / + ``ticks_spacing`` kwargs it replaces are deprecated — still accepted, + but they emit a :class:`DeprecationWarning`. ``False`` hides it, + ``None`` uses the default. Default ``None``. + points (np.ndarray or PointOverlay, optional): + Point overlay. A 3-column array ``(value, row, col)`` draws unstyled + points; to style them pass a ``pyramids.plot.PointOverlay(points, + color=…, size=…, label_color=…, label_size=…)`` instead (on cleopatra + >= 0.26 the loose ``point_color`` / ``point_size`` / ``pid_color`` / + ``pid_size`` kwargs are deprecated). Default ``None``. + animation_axis_values (sequence, optional): + Per-frame labels for the animation, one per timestep. Defaults to the + collection's ``time`` axis when set (e.g. dates parsed by + ``read_multiple_files``), else ``range(time_length)`` (index labels). + Pass a sequence to override (e.g. ``range(2000, 2024)``); it must carry + exactly one label per timestep or a :class:`ValueError` is raised. + Default ``None``. **kwargs: | Parameter | Type | Description | |----------------------------|-----------------------|-------------| - | animation_axis_values | sequence, optional | Per-frame labels for the animation, one per timestep. Defaults to the collection's `time` axis when set (e.g. dates parsed by `read_multiple_files`), else `range(time_length)` (index labels). Pass a sequence here to override (e.g. `range(2000, 2024)`). | - | points | array \| PointOverlay | Point overlay. A 3-column array (value to display, row index, column index) draws unstyled points. To style them, pass a `cleopatra.glyphs.gridded.array_glyph.PointOverlay(points, color=..., size=..., label_color=..., label_size=...)` instead — on cleopatra >= 0.26 the loose `point_color` / `point_size` / `pid_color` / `pid_size` kwargs are deprecated; set the styling on the `PointOverlay` instead. | | figsize | tuple, optional | Figure size. Default is `(8, 8)`. | | title | str, optional | Title of the plot. Default is `'Total Discharge'`. | | title_size | int, optional | Title size. Default is `15`. | - | colorbar | bool \| ColorBar, optional | Colour-bar spec `pyramids.plot.ColorBar(label=…, length=…, orientation=…, label_size=…, label_rotation=…, label_location=…, ticks_spacing=…)` (cleopatra >= 0.28). The loose `cbar_*` / `ticks_spacing` kwargs it replaces are deprecated — still accepted, but they emit a `DeprecationWarning`. `False` hides it, `None` uses the default. | | color_scale | str, optional | Color-scale mode (default `"linear"`): one of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styling.styles.ColorScale` member. Integer codes are no longer accepted. | | gamma | float, optional | Exponent for `color_scale="power"`. Default is `1/2`. | | line_threshold | float, optional | `linthresh` for `color_scale="sym-lognorm"`. Default is `0.0001`. | diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index ac2f3c5d17..84c13aa0bf 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -873,6 +873,21 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo draws a shaded-relief / coastline layer instead. Passing a ``dict`` here is a deprecated alias for ``Basemap`` (emits a ``DeprecationWarning``). Default is ``None``. Requires the ``[viz]`` extra. + colorbar (bool or ColorBar, optional): + Colour-bar spec. A ``pyramids.plot.ColorBar(label=…, length=…, + orientation=…, …)`` (cleopatra >= 0.28) draws a configured bar; the loose + ``cbar_*`` / ``ticks_spacing`` kwargs it replaces are deprecated (still + accepted, but they emit a :class:`DeprecationWarning`). ``False`` hides the + bar, ``None`` uses cleopatra's default. Default is ``None``. + points (np.ndarray or PointOverlay, optional): + Point overlay. A 3-column array ``(value, row, col)`` draws unstyled + points; pass a ``pyramids.plot.PointOverlay(points, color=…, size=…, …)`` + to style them. Default is ``None``. + kind (str, optional): + Renderer to use. ``"auto"`` (default) picks per data; otherwise one of + ``"imshow"`` / ``"pcolormesh"`` / ``"contour"`` / ``"contourf"``. + title (str, optional): + Axes title. Default is ``None`` (cleopatra's default title). rgb_options (dict, optional): Grouped Sentinel-imagery kwargs. Accepted keys: ``"rgb"``, ``"surface_reflectance"``, ``"cutoff"``, diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index 79c6321fd6..a44bdf1ea0 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -1764,6 +1764,17 @@ def plot( # type: ignore[override] Existing matplotlib Axes to draw into. Defaults to None. figsize (tuple, optional): Figure size in inches. Defaults to None. + colorbar (bool or ColorBar, optional): + Colour-bar spec. A ``pyramids.plot.ColorBar(label=…, …)`` + (cleopatra >= 0.28) draws a configured bar; ``False`` hides it and + ``None`` uses cleopatra's default. Only forwarded when set. Default + is ``None``. + points (np.ndarray or PointOverlay, optional): + Point overlay for the static / animate paths. A 3-column array + ``(value, row, col)`` draws unstyled points; pass a + ``pyramids.plot.PointOverlay(...)`` to style them. Only forwarded + when set — the multi-panel ``facet`` path has no ``points`` + parameter and raises if one is supplied. Default is ``None``. **kwargs: Additional keyword arguments forwarded to :meth:`Analysis.plot `. diff --git a/tests/netcdf/plot/test_hoisted_render_params.py b/tests/netcdf/plot/test_hoisted_render_params.py new file mode 100644 index 0000000000..b4905b6516 --- /dev/null +++ b/tests/netcdf/plot/test_hoisted_render_params.py @@ -0,0 +1,174 @@ +"""Hoisted ``colorbar`` / ``points`` params on :meth:`NetCDF.plot`. + +The cleopatra-0.29 alignment lifts ``colorbar`` and ``points`` out of the opaque +``**kwargs`` and onto the ``NetCDF.plot`` signature (parity with ``Dataset.plot`` / +``DatasetCollection.plot``). Because ``NetCDF.plot`` is a multi-mode facade +(static / animate / facet) and cleopatra's facet path rejects ``points`` outright, +the hoisted params are forwarded **only when set** — a default ``None`` must never +reach the render call. These tests pin both the parity forwarding and that +conditional-injection contract. +""" + +from __future__ import annotations + +from unittest.mock import patch + +import numpy as np +import pytest + +from pyramids.netcdf import FacetSpec +from tests.netcdf.conftest import make_plot_3d_nc + +pytestmark = pytest.mark.plot + +pytest.importorskip("cleopatra", minversion="0.29", reason="needs cleopatra >= 0.29") +_cleo_config = pytest.importorskip("cleopatra.config", reason="cleopatra not installed") +_cleo_config.Config.set_matplotlib_backend("Agg") +_cleo_array = pytest.importorskip( + "cleopatra.glyphs.gridded.array_glyph", reason="cleopatra not installed" +) +ArrayGlyph = _cleo_array.ArrayGlyph +FacetGrid = _cleo_array.FacetGrid +ColorBar = _cleo_array.ColorBar +plt = pytest.importorskip("matplotlib.pyplot", reason="cleopatra not installed") + + +@pytest.fixture(autouse=True) +def _close_matplotlib_figures(): + """Close every figure after each test so the suite does not leak them. + + Yields: + None: control returns to the test; figures are closed on teardown. + """ + yield + plt.close("all") + + +def _points() -> np.ndarray: + """Build a minimal cleopatra point overlay for the 5x5 plot grid. + + Returns: + np.ndarray: A ``(2, 3)`` array of ``(value, row, col)`` rows whose row/col + indices fall inside the ``make_plot_3d_nc`` 5x5 raster. + """ + return np.array([[1.0, 1, 1], [2.0, 2, 2]], dtype="float64") + + +class TestNetCDFHoistedRenderParamsStatic: + """The hoisted ``colorbar`` / ``points`` params render on the static path. + + cleopatra rejects an unknown keyword, so a clean :class:`ArrayGlyph` proves the + param was forwarded to ``ArrayGlyph.plot`` rather than silently dropped. + """ + + def test_colorbar_spec_renders(self): + """A ``ColorBar`` spec on the static path renders to an ``ArrayGlyph``. + + Test scenario: + ``nc.plot(variable="t2m", colorbar=ColorBar(...))`` on a 3-D variable + returns an :class:`ArrayGlyph`, proving the hoisted ``colorbar`` reached + cleopatra. + """ + nc = make_plot_3d_nc() + glyph = nc.plot(variable="t2m", colorbar=ColorBar(label="temp")) + assert isinstance(glyph, ArrayGlyph), f"expected ArrayGlyph, got {type(glyph)}" + + def test_colorbar_false_renders(self): + """``colorbar=False`` suppresses the colour bar and still renders. + + Test scenario: + The boolean ``False`` form of the hoisted param forwards cleanly and + yields an :class:`ArrayGlyph`. + """ + nc = make_plot_3d_nc() + glyph = nc.plot(variable="t2m", colorbar=False) + assert isinstance(glyph, ArrayGlyph), f"expected ArrayGlyph, got {type(glyph)}" + + def test_points_overlay_renders(self): + """A ``points`` overlay on the static path renders to an ``ArrayGlyph``. + + Test scenario: + ``nc.plot(variable="t2m", points=<(2, 3) array>)`` forwards the hoisted + ``points`` param to cleopatra and returns an :class:`ArrayGlyph`. + """ + nc = make_plot_3d_nc() + glyph = nc.plot(variable="t2m", points=_points()) + assert isinstance(glyph, ArrayGlyph), f"expected ArrayGlyph, got {type(glyph)}" + + +class TestNetCDFHoistedParamConditionalInjection: + """Defaults are not injected; explicit values are. + + The forwarding is spied at ``Analysis.plot`` — the render boundary the facade + hands off to — to assert the presence/absence of the hoisted keys. + """ + + def test_defaults_not_injected(self): + """Omitted ``colorbar`` / ``points`` never reach the render call. + + Test scenario: + A bare ``nc.plot(variable="t2m")`` must not forward ``colorbar`` or + ``points`` (their default ``None`` is dropped), so the facet path — which + rejects ``points`` — is never handed a spurious key. + """ + nc = make_plot_3d_nc() + var = nc.get_variable("t2m") + with patch.object(type(var.analysis), "plot", autospec=True) as mock_plot: + mock_plot.return_value = "ok" + nc.plot(variable="t2m") + forwarded = mock_plot.call_args.kwargs + assert "points" not in forwarded, f"points must not be injected: {forwarded}" + assert "colorbar" not in forwarded, ( + f"colorbar must not be injected by default: {forwarded}" + ) + + def test_points_injected_when_set(self): + """An explicit ``points`` value is forwarded to the render call. + + Test scenario: + ``nc.plot(variable="t2m", points=)`` forwards ``points`` verbatim + to ``Analysis.plot``. + """ + nc = make_plot_3d_nc() + var = nc.get_variable("t2m") + pts = _points() + with patch.object(type(var.analysis), "plot", autospec=True) as mock_plot: + mock_plot.return_value = "ok" + nc.plot(variable="t2m", points=pts) + forwarded = mock_plot.call_args.kwargs + assert "points" in forwarded, f"points must be forwarded when set: {forwarded}" + assert forwarded["points"] is pts, "points must forward the exact array" + + +class TestNetCDFFacetPointsContract: + """The facet path: default ``points`` is fine; an explicit one is rejected. + + This is the exact contract the conditional injection exists to preserve — a + default ``None`` must not reach cleopatra's facet (which has no ``points`` + parameter), while an explicit ``points`` correctly surfaces the error. + """ + + def test_facet_without_points_renders(self): + """A facet plot with no ``points`` renders to a ``FacetGrid``. + + Test scenario: + ``nc.plot(variable="t2m", facet=FacetSpec(col="time"))`` must succeed — + the default ``points`` is not injected, so the facet path is not handed a + keyword it rejects. + """ + nc = make_plot_3d_nc(n_times=4) + grid = nc.plot(variable="t2m", facet=FacetSpec(col="time")) + assert isinstance(grid, FacetGrid), f"expected FacetGrid, got {type(grid)}" + + def test_facet_with_explicit_points_raises(self): + """An explicit ``points`` on the facet path raises a clear error. + + Test scenario: + cleopatra's facet has no ``points`` parameter, so + ``nc.plot(variable="t2m", facet=FacetSpec(col="time"), points=)`` + raises ``ValueError`` naming the offending keyword. + """ + nc = make_plot_3d_nc(n_times=4) + with pytest.raises(ValueError, match="points") as exc_info: + nc.plot(variable="t2m", facet=FacetSpec(col="time"), points=_points()) + assert "points" in str(exc_info.value), f"unexpected error: {exc_info.value}" From 408a7f7cd08a4f24021d0a1cb587cd7cc16f5a7d Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 18:10:55 +0200 Subject: [PATCH 06/30] refactor(plot): adopt cleopatra 0.30's grouped render-parameter objects cleopatra's next restructure (grouped plot/animate/facet parameters) removed the flat styling keywords and the shims that accepted them, so a loose `color_scale=` / `style=` / `levels=` / bare-array `points=` now raises. Keep pyramids' stable loose-kwarg facade and translate to the typed group objects at the single render boundary: - add `_build_grouped_render_specs` folding loose color_scale/gamma/line_threshold/ line_scale/bounds/midpoint -> ColorScaling, levels/label_kw -> Contour, display_cell_value/num_size/background_color_threshold -> CellValues, and style/hillshade -> DataStyle (preserving the style=None / falsy-hillshade no-op). - render_array: build the groups, wrap a bare points array in PointOverlay, forward the groups on the plot/animate/facet render call, and translate facet col_coords/row_coords -> PanelLabels and figsize -> figure_size. - plot_mesh_data: same translation for MeshGlyph.plot (no cell-value overlay). - drop the obsolete style/hillshade version gate (the grouped API requires cleopatra >= 0.30, so the >= 0.24 upgrade hint no longer applies); update the tests that asserted the old loose-kwarg forwarding / version gate. - pin the cleopatra git source to the exact rev carrying the restructure so pixi re-fetches and the build is reproducible. Full plot surface (dataset/netcdf/ugrid/basemap/feature/collection): 1669 passed; mypy clean; doctests pass. --- pixi.lock | 92 +++++----- pyproject.toml | 11 +- src/pyramids/dataset/_plot_helpers.py | 243 ++++++++++++++++--------- src/pyramids/netcdf/ugrid/plot.py | 8 + tests/dataset/plot/test_plot_engine.py | 106 +++-------- tests/ugrid/test_plot.py | 30 ++- 6 files changed, 255 insertions(+), 235 deletions(-) diff --git a/pixi.lock b/pixi.lock index 406ff9433f..ddf7e63896 100644 --- a/pixi.lock +++ b/pixi.lock @@ -137,7 +137,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl @@ -253,7 +253,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl @@ -363,7 +363,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl @@ -473,7 +473,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl @@ -577,7 +577,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl @@ -803,7 +803,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1167,7 +1167,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1525,7 +1525,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1881,7 +1881,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -2232,7 +2232,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -2596,7 +2596,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2951,7 +2951,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3300,7 +3300,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3650,7 +3650,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3995,7 +3995,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -4307,7 +4307,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4615,7 +4615,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4917,7 +4917,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5217,7 +5217,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5514,7 +5514,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5868,7 +5868,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6222,7 +6222,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6570,7 +6570,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6916,7 +6916,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7257,7 +7257,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7559,7 +7559,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7868,7 +7868,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8170,7 +8170,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8470,7 +8470,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8766,7 +8766,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9077,7 +9077,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl @@ -9384,7 +9384,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9684,7 +9684,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9982,7 +9982,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10276,7 +10276,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10585,7 +10585,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10892,7 +10892,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11193,7 +11193,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11492,7 +11492,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11787,7 +11787,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12098,7 +12098,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12407,7 +12407,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12710,7 +12710,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -13011,7 +13011,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -13308,7 +13308,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 + - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -36482,7 +36482,7 @@ packages: - pystac-client>=0.8.0 ; extra == 'stac' - stac-asset>=0.4.7 ; extra == 'stac' requires_python: '>=3.11,<4' -- pypi: git+https://github.com/serapeum-org/cleopatra.git?branch=main#3ddca38a0536f1cc5cb6cba67aa1bf0ad9df39a5 +- pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 name: cleopatra version: 0.29.0 requires_dist: diff --git a/pyproject.toml b/pyproject.toml index b1224ca581..0da53c7292 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -314,12 +314,15 @@ platforms = ["win-64", "linux-64", "linux-aarch64", "osx-arm64", "osx-64"] [tool.pixi.pypi-dependencies] pyramids-gis = { path = ".", editable = true } -# cleopatra's module restructure lands in its 0.30.0 release; until then pyramids -# needs it from git main (which carries the new subpackage layout but still -# self-reports 0.29.0). Dev/CI track main here; the published [viz] extra stays a +# cleopatra's 0.30.0 restructure lands in that release; until then pyramids needs it +# from git. Pinned to an exact rev (not `branch = "main"`) so the build is reproducible +# and pixi actually re-fetches on a bump — this commit carries BOTH the subpackage +# layout AND the grouped render-parameter objects (ColorScaling / Contour / CellValues / +# DataStyle / PointOverlay / PanelLabels), which removed the loose styling keywords. +# It still self-reports 0.29.0. Dev/CI track this rev; the published [viz] extra stays a # plain version spec so built wheels carry no direct-URL metadata (PyPI rejects it). # Remove this and bump the [viz] floor to `>=0.30.0` once cleopatra 0.30.0 releases. -cleopatra = { git = "https://github.com/serapeum-org/cleopatra.git", branch = "main", extras = ["tiles"] } +cleopatra = { git = "https://github.com/serapeum-org/cleopatra.git", rev = "cfa90c3322c4e6c0d8837d54af874172a02c1b50", extras = ["tiles"] } [tool.pixi.tasks] main = { cmd = "pytest -vvv --cov=src/pyramids -sv -m 'not plot and not xarray' --cov-report=xml", description = "Run the main test suite" } diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index 8e0140e25e..f2fa26c0e2 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -67,7 +67,6 @@ from pyproj import CRS from pyproj.exceptions import CRSError -from pyramids.base._errors import OptionalPackageDoesNotExist from pyramids.base._utils import require_cleopatra # `add_basemap` is imported at top-level so existing test patches that @@ -162,57 +161,6 @@ def _unwrap_geographic_longitude( return result -def _guard_style_hillshade(kwargs: dict[str, Any], option_keys: Any) -> None: - """Normalise and version-gate the ``style`` / ``hillshade`` plot presets. - - Shared by the raster (:func:`render_array`, ``ArrayGlyph``) and mesh - (:func:`mesh_render`, ``MeshGlyph``) dispatch so both paths handle cleopatra's - data-style presets identically. - - ``style`` / ``hillshade`` were added to cleopatra's glyphs in 0.24. On an older - cleopatra the kwargs would fall through to a cryptic "Unknown option" error deep - in the render call; feature-detect each key against ``option_keys`` and raise a - clear upgrade hint instead. - - A ``None`` / ``False`` ``hillshade`` requests no shading, so it is popped from - ``kwargs`` in place — making it a true no-op on ANY cleopatra version (a pre-0.24 - build that rejects the unknown kwarg included). ``{}`` is falsy but is an - affirmative "shade with default parameters", so the drop uses identity (only - ``None`` / ``False``), not truthiness — an empty-dict ``hillshade`` is still - gated/forwarded. An explicit ``style=None`` requests no preset and is likewise - popped in place, symmetric with the ``hillshade`` drop. Each remaining key is - checked against its own ``option_keys`` membership so a build shipping only one - of the two features is handled precisely. An invalid ``style`` *name* is left to - cleopatra, which already raises a ``ValueError`` listing the valid ``DATA_STYLES`` - keys. (issue #737) - - Args: - kwargs: The render kwargs; mutated in place to drop a no-op ``style`` / - ``hillshade``. - option_keys: The glyph's declared option set (``.option_keys()``). - - Raises: - OptionalPackageDoesNotExist: A ``style`` / ``hillshade`` preset was - requested but the installed cleopatra's glyph does not support it. - """ - if kwargs.get("style") is None: - # An explicit ``style=None`` requests no preset — drop it so it never - # reaches cleopatra (a true no-op on any version), symmetric with the - # ``hillshade`` handling below. - kwargs.pop("style", None) - hillshade = kwargs.get("hillshade") - if hillshade is None or hillshade is False: - kwargs.pop("hillshade", None) - style_unsupported = "style" in kwargs and "style" not in option_keys - hillshade_unsupported = "hillshade" in kwargs and "hillshade" not in option_keys - if style_unsupported or hillshade_unsupported: - raise OptionalPackageDoesNotExist( - "`style=` / `hillshade=` plot presets require cleopatra >= 0.24. " - "Upgrade with: pip install -U 'pyramids-gis[viz]' (or " - "pip install -U 'cleopatra>=0.24')." - ) - - # Loose plot kwargs cleopatra 0.28 deprecated in favour of the typed specs # (ColorBar / PointOverlay). ``render_array`` translates them to the spec at the # render boundary (see ``_migrate_deprecated_plot_specs``) so cleopatra only ever @@ -317,6 +265,123 @@ def _migrate_deprecated_plot_specs( kwargs["points"] = point_overlay_cls(points, **style) +def _build_grouped_render_specs( + kwargs: dict[str, Any], *, include_cells: bool = True +) -> dict[str, Any]: + """Fold pyramids' loose scale / contour / cell / style kwargs into cleopatra groups. + + cleopatra 0.30 replaced the flat styling keywords on ``plot`` / ``animate`` / ``facet`` + with typed *group objects* and removed the shims that accepted the loose forms, so a + loose ``color_scale=`` / ``style=`` / ``levels=`` now raises. pyramids keeps its stable + loose-kwarg facade and translates them here, at the single render boundary, mirroring + :func:`_migrate_deprecated_plot_specs`. The loose keys are **popped** from ``kwargs`` in + place and the built group objects are returned keyed by their cleopatra render-method + parameter name (``color`` / ``contour`` / ``cells`` / ``data_style``). Only a group with + at least one set field is built, so an untouched call forwards nothing. + + Mapping (loose keyword -> group field): + + * ``color_scale`` / ``gamma`` / ``line_threshold`` / ``line_scale`` / ``bounds`` / + ``midpoint`` -> :class:`~cleopatra.styling.scaling.ColorScaling` (``midpoint`` maps to + its ``center`` field; ``color_scale`` selects the ``kind`` via ``ColorScale``). + * ``levels`` / ``label_kw`` -> :class:`~cleopatra.styling.params.Contour`. + * ``display_cell_value`` / ``num_size`` / ``background_color_threshold`` -> + :class:`~cleopatra.styling.params.CellValues` (``show`` / ``size`` / + ``background_threshold``). + * ``style`` / ``hillshade`` -> :class:`~cleopatra.styling.params.DataStyle`. A no-op + ``style=None`` or ``hillshade`` of ``None`` / ``False`` is dropped without building a + ``DataStyle`` (an empty-dict ``hillshade`` is "shade with defaults" and is kept). + + Args: + kwargs: The render kwargs; mutated in place to pop the folded loose keys. + include_cells: When ``False`` (the mesh path — ``MeshGlyph.plot`` has no ``cells`` + parameter) the cell-value keys are left untouched. + + Returns: + A dict of the built cleopatra group objects, keyed by render-method parameter name. + + Raises: + ValueError: If ``color_scale`` is not a recognised + :class:`~cleopatra.styling.styles.ColorScale` value; the message lists the valid + options. + """ + from cleopatra.styling.params import CellValues, Contour, DataStyle + from cleopatra.styling.scaling import ColorScaling + from cleopatra.styling.styles import ColorScale + + specs: dict[str, Any] = {} + + scale_keys = ( + "color_scale", + "gamma", + "line_threshold", + "line_scale", + "bounds", + "midpoint", + ) + if any(k in kwargs for k in scale_keys): + color_scale = kwargs.pop("color_scale", None) + try: + kind = ( + ColorScale(color_scale) + if color_scale is not None + else ColorScale.LINEAR + ) + except ValueError: + valid = [s.value for s in ColorScale] + raise ValueError( + f"Unsupported color_scale {color_scale!r}; valid options: {valid}." + ) from None + scale_params: dict[str, Any] = {"kind": kind} + for src, dst in ( + ("gamma", "gamma"), + ("line_threshold", "line_threshold"), + ("line_scale", "line_scale"), + ("bounds", "bounds"), + ("midpoint", "center"), + ): + if src in kwargs: + scale_params[dst] = kwargs.pop(src) + specs["color"] = ColorScaling(**scale_params) + + if "levels" in kwargs or "label_kw" in kwargs: + contour_params: dict[str, Any] = {} + if "levels" in kwargs: + contour_params["levels"] = kwargs.pop("levels") + if "label_kw" in kwargs: + contour_params["label_kw"] = kwargs.pop("label_kw") + specs["contour"] = Contour(**contour_params) + + cell_keys = ("display_cell_value", "num_size", "background_color_threshold") + if include_cells and any(k in kwargs for k in cell_keys): + cell_params: dict[str, Any] = {} + if "display_cell_value" in kwargs: + cell_params["show"] = kwargs.pop("display_cell_value") + if "num_size" in kwargs: + cell_params["size"] = kwargs.pop("num_size") + if "background_color_threshold" in kwargs: + cell_params["background_threshold"] = kwargs.pop("background_color_threshold") + specs["cells"] = CellValues(**cell_params) + + # style/hillshade -> DataStyle, honouring the historical no-op semantics: an explicit + # ``style=None`` requests no preset and a ``hillshade`` of ``None`` / ``False`` requests + # no shading, so neither builds a + # DataStyle. An empty-dict ``hillshade`` ({}) is falsy but means "shade with defaults", + # so identity checks (not truthiness) gate it. Always pop so a no-op never reaches + # cleopatra as a now-rejected loose keyword. + style = kwargs.pop("style", None) + hillshade = kwargs.pop("hillshade", None) + style_params: dict[str, Any] = {} + if style is not None: + style_params["style"] = style + if hillshade is not None and hillshade is not False: + style_params["hillshade"] = hillshade + if style_params: + specs["data_style"] = DataStyle(**style_params) + + return specs + + def render_array( *, arr: np.ndarray | None, @@ -533,9 +598,12 @@ def render_array( """ require_cleopatra() from cleopatra.basemap.geo import Basemap - from cleopatra.glyphs.gridded.array_glyph import ArrayGlyph, PointOverlay + from cleopatra.glyphs.gridded.array_glyph import ( + ArrayGlyph, + PanelLabels, + PointOverlay, + ) from cleopatra.styling.colorbar import ColorBar - from cleopatra.styling.styles import ColorScale # Deprecate + translate the loose plot kwargs to the typed specs (cbar_* -> # ColorBar, point_*/pid_* -> PointOverlay, dict basemap -> Basemap) so every @@ -596,25 +664,26 @@ def render_array( # The stretch parameters have been consumed by ``prepare_array`` above; # null them so the constructor call below cannot re-read inert values. rgb = surface_reflectance = cutoff = percentile = None - # Fail fast on an invalid ``color_scale`` with a pyramids-side message - # that lists the valid options, instead of deferring to a less-targeted - # cleopatra error deep in the render call. ``ColorScale`` lookup is - # case-insensitive, so any-case valid values pass through unchanged. - color_scale = kwargs.get("color_scale") - if color_scale is not None: - try: - ColorScale(color_scale) - except ValueError: - valid = [s.value for s in ColorScale] - raise ValueError( - f"Unsupported color_scale {color_scale!r}; valid options: {valid}." - ) from None + # cleopatra 0.30 replaced the flat styling keywords on plot/animate/facet with typed + # group objects and removed the loose-kwarg shims, so translate pyramids' loose + # color_scale / gamma / bounds / midpoint / levels / display_cell_value / style / + # hillshade (etc.) into the cleopatra ColorScaling / Contour / CellValues / DataStyle + # groups here, at the single render boundary. ``color_scale`` is validated inside the + # helper with a pyramids-side message listing the valid options, and a no-op style / + # hillshade is dropped. The built groups are render-method params, so they ride on the + # plot / animate / facet call (folded into ``render_kwargs`` below). + group_specs = _build_grouped_render_specs(kwargs) + + # A bare ``(N, 3)`` points array is no longer auto-wrapped by cleopatra (it raises on + # the overlay-drawing kinds, and is silently ignored on contour); wrap it in a + # ``PointOverlay`` so pyramids callers can keep passing a plain array. A points value + # already migrated to a ``PointOverlay`` by ``_migrate_deprecated_plot_specs`` (loose + # ``point_*`` present) is left as-is. + points = kwargs.get("points") + if points is not None and not isinstance(points, PointOverlay): + kwargs["points"] = PointOverlay(np.asarray(points)) - # Normalise + version-gate the style/hillshade presets (shared with the mesh - # path). Mutates kwargs to drop a no-op hillshade; raises a clear upgrade hint - # on a cleopatra whose ArrayGlyph lacks the presets. (issue #737) option_keys = ArrayGlyph.option_keys() - _guard_style_hillshade(kwargs, option_keys) # Unwrap a wrapping geographic longitude before handing curvilinear coords # to cleopatra, so its pcolormesh doesn't smear a ~178-degree quad across @@ -671,6 +740,11 @@ def render_array( ctor_kwargs[key] = value else: render_kwargs[key] = value + # The cleopatra group objects translated from pyramids' loose kwargs + # (``color`` / ``contour`` / ``cells`` / ``data_style``) are render-method params on + # ``plot`` / ``animate`` / ``facet``, not constructor options, so fold them into the + # render bucket (the animate merge below then picks them up too). + render_kwargs.update(group_specs) # The ``"animate"`` path only flows kwargs into ``cleo.animate(...)``, # not the constructor — keys like ``interval`` are valid for animate # but not in cleopatra's ``DEFAULT_OPTIONS`` and would trigger an @@ -791,7 +865,18 @@ def _apply_basemap(target_ax: Any) -> None: "(basemap='') for per-panel tiles, or plot without faceting " "for a relief/features basemap." ) - result = cleo.facet(**facet_kwargs, **render_kwargs) + # cleopatra 0.30 renamed facet's ``figsize`` to ``figure_size`` and moved the + # per-panel coordinate labels onto a ``PanelLabels`` group (``col`` / ``row``). + # Translate the loose facet_kwargs (built by the NetCDF facet path) into the new + # names so the NetCDF facet builder can keep emitting the historical spelling. + facet_call = dict(facet_kwargs) + col_coords = facet_call.pop("col_coords", None) + row_coords = facet_call.pop("row_coords", None) + if col_coords is not None or row_coords is not None: + facet_call["labels"] = PanelLabels(col=col_coords, row=row_coords) + if "figsize" in facet_call: + facet_call["figure_size"] = facet_call.pop("figsize") + result = cleo.facet(**facet_call, **render_kwargs) if tile_basemap: # Every facet panel renders the same spatial domain (cleopatra # reuses the parent extent / curvilinear coords across panels), @@ -900,19 +985,11 @@ def mesh_render( if basemap and basemap_epsg is None: raise ValueError("Dataset must have a CRS (epsg) to use basemap.") require_cleopatra() - from cleopatra.glyphs.gridded.mesh_glyph import MeshGlyph - + # ``plot_mesh_data`` translates the loose style/hillshade (and the other grouped + # styling kwargs) into cleopatra's ``DataStyle`` / ``ColorScaling`` / ``Contour`` + # groups at the MeshGlyph.plot boundary, so no pre-processing is needed here. from pyramids.netcdf.ugrid.plot import plot_mesh_data - # Normalise + version-gate the style/hillshade presets against MeshGlyph, the - # same way render_array does against ArrayGlyph, so the mesh path exposes - # cleopatra's data-style presets with a clean upgrade hint on older cleopatra. - # Note: unlike the raster path (where style is an ArrayGlyph *constructor* - # option), plot_mesh_data forwards style/hillshade to MeshGlyph.plot(), not the - # constructor. Feature-detecting against option_keys() therefore assumes cleopatra - # declares them there in lock-step with .plot() accepting them (true on 0.24). - _guard_style_hillshade(kwargs, MeshGlyph.option_keys()) - result = plot_mesh_data(mesh, data, location=location, **kwargs) if basemap: # The guard above already proved basemap_epsg is set when basemap is diff --git a/src/pyramids/netcdf/ugrid/plot.py b/src/pyramids/netcdf/ugrid/plot.py index 65b2c811fe..fb66889e78 100644 --- a/src/pyramids/netcdf/ugrid/plot.py +++ b/src/pyramids/netcdf/ugrid/plot.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any from pyramids.base._utils import require_cleopatra +from pyramids.dataset._plot_helpers import _build_grouped_render_specs from pyramids.netcdf.ugrid.mesh import Mesh2d if TYPE_CHECKING: @@ -155,6 +156,12 @@ def plot_mesh_data( plot_kwargs["vmax"] = vmax plot_kwargs.update(kwargs) + # cleopatra 0.30 replaced MeshGlyph.plot's loose styling keywords with typed group + # objects; translate pyramids' loose color_scale / gamma / bounds / midpoint / levels / + # style / hillshade into cleopatra's ColorScaling / Contour / DataStyle groups. MeshGlyph + # has no cell-value overlay, so the cell-value keys are left untouched (include_cells). + group_specs = _build_grouped_render_specs(plot_kwargs, include_cells=False) + glyph.plot( data, location=location, @@ -162,6 +169,7 @@ def plot_mesh_data( edgecolor=edgecolor, colorbar=colorbar, title=title, + **group_specs, **plot_kwargs, ) return glyph diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index 7873f4b09b..6b3b2c29f9 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -5,7 +5,6 @@ import numpy as np import pytest -from pyramids.base._errors import OptionalPackageDoesNotExist from pyramids.dataset import Dataset from pyramids.dataset._plot_helpers import render_array from pyramids.dataset.engines import Analysis @@ -145,15 +144,16 @@ def facet(self, **kwargs): return _FakeGlyph, ctor_seen, plot_seen, animate_seen, facet_seen, animate_args - def test_constructor_owns_cmap_vmin_vmax_levels_cbar(self): - """Style/scale kwargs all land on the constructor for plot mode. + def test_constructor_owns_cmap_vmin_vmax_cbar(self): + """Plain style/scale kwargs land on the constructor for plot mode. Test scenario: - ``cmap``, ``vmin``, ``vmax``, ``levels``, ``cbar_kwargs`` - must reach ``ArrayGlyph.__init__`` so cleopatra's - ``default_options`` is set in one place. None of them may - also reach ``ArrayGlyph.plot``; otherwise the value would be - overwritten twice (the PR-6 D-4 fix). + ``cmap``, ``vmin``, ``vmax``, ``cbar_kwargs`` must reach + ``ArrayGlyph.__init__`` so cleopatra's ``default_options`` is set in one + place. None of them may also reach ``ArrayGlyph.plot``; otherwise the + value would be overwritten twice (the PR-6 D-4 fix). ``levels`` is no + longer a constructor option — cleopatra 0.30 moved it onto the + ``contour=Contour(...)`` render group (asserted separately below). """ fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(101) @@ -169,11 +169,16 @@ def test_constructor_owns_cmap_vmin_vmax_levels_cbar(self): levels=10, cbar_kwargs={"orientation": "horizontal"}, ) - for key in ("cmap", "vmin", "vmax", "levels", "cbar_kwargs"): + for key in ("cmap", "vmin", "vmax", "cbar_kwargs"): assert key in ctor, f"`{key}` must land on the constructor; ctor={ctor}" assert key not in plot, ( f"`{key}` must NOT also reach cleo.plot; plot={plot}" ) + assert "levels" not in ctor, "`levels` folds into contour, not the ctor" + contour = plot.get("contour") + assert contour is not None and contour.levels == 10, ( + f"`levels` must fold into contour=Contour(levels=10); plot={plot}" + ) def test_render_call_only_kwargs_reach_plot(self): """Render-call-only kwargs reach ``ArrayGlyph.plot`` and nothing else. @@ -609,23 +614,6 @@ def test_style_and_hillshade_animate_renders(self): ) assert isinstance(glyph, ArrayGlyph) - def test_hillshade_only_on_old_cleopatra_raises_upgrade_hint(self): - """``hillshade=`` alone on a build lacking it raises the upgrade hint. - - Test scenario: - The guard checks each key independently. Simulate a cleopatra that - supports ``style`` but not ``hillshade``; a truthy ``hillshade=`` - with no ``style=`` must still raise the >= 0.24 upgrade error. - """ - rng = np.random.default_rng(744) - arr = rng.random((5, 5)).astype("float32") - style_only = (ArrayGlyph.option_keys() - {"hillshade"}) | {"style"} - with patch.object(ArrayGlyph, "option_keys", return_value=style_only): - with pytest.raises(OptionalPackageDoesNotExist, match="cleopatra >= 0.24"): - render_array( - arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade=True - ) - @pytest.mark.parametrize("value", [False, None]) def test_falsy_hillshade_is_dropped_not_forwarded(self, value): """A ``None``/``False`` ``hillshade`` is dropped, never reaching cleopatra. @@ -669,35 +657,20 @@ def test_empty_dict_hillshade_is_forwarded(self): Test scenario: ``{}`` is falsy but documented as "shade with default parameters", - so unlike ``False`` it must NOT be dropped — it flows to the glyph - constructor like any other affirmative hillshade request. + so unlike ``False`` it must NOT be dropped — it is folded into a + cleopatra ``DataStyle(hillshade={})`` on the render call (cleopatra + 0.30 moved ``hillshade`` off the loose kwargs onto ``data_style=``). """ - fake_cls, ctor, _plot, *_ = TestRenderArrayKwargRouting._capture_calls() + fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() rng = np.random.default_rng(746) arr = rng.random((5, 5)).astype("float32") with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade={} ) - assert ctor.get("hillshade") == {}, "affirmative hillshade={} must be forwarded" - - def test_empty_dict_hillshade_on_old_cleopatra_raises_upgrade_hint(self): - """``hillshade={}`` on a build lacking hillshade raises the upgrade hint. - - Test scenario: - The affirmative empty-dict form must be gated exactly like - ``hillshade=True`` — an old cleopatra (``hillshade`` absent from - ``option_keys()``) yields the clear >= 0.24 error, not the cryptic - downstream "Unknown option". - """ - rng = np.random.default_rng(747) - arr = rng.random((5, 5)).astype("float32") - old_keys = ArrayGlyph.option_keys() - {"style", "hillshade"} - with patch.object(ArrayGlyph, "option_keys", return_value=old_keys): - with pytest.raises(OptionalPackageDoesNotExist, match="cleopatra >= 0.24"): - render_array( - arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade={} - ) + data_style = plot.get("data_style") + assert data_style is not None, "affirmative hillshade={} must build a DataStyle" + assert data_style.hillshade == {}, "hillshade={} must reach the DataStyle" @pytest.mark.skipif( not _supports_style, reason="cleopatra < 0.24 has no style presets" @@ -720,43 +693,6 @@ def test_unknown_style_name_surfaces_cleopatra_valueerror(self): style="definitely_not_a_style", ) - def test_style_on_old_cleopatra_raises_upgrade_hint(self): - """``style=`` on a cleopatra without preset support raises a clear hint. - - Test scenario: - Simulate cleopatra < 0.24 by hiding ``style`` from - ``option_keys()``. The scoped guard in ``render_array`` must raise - :class:`OptionalPackageDoesNotExist` pointing at the >= 0.24 upgrade - instead of letting a cryptic "Unknown option" surface deep in the - render call. - """ - rng = np.random.default_rng(740) - arr = rng.random((5, 5)).astype("float32") - old_keys = ArrayGlyph.option_keys() - {"style", "hillshade"} - with patch.object(ArrayGlyph, "option_keys", return_value=old_keys): - with pytest.raises(OptionalPackageDoesNotExist, match="cleopatra >= 0.24"): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - style="topography", - ) - - def test_no_preset_kwargs_unaffected_on_old_cleopatra(self): - """Basic plotting is untouched by the guard on an older cleopatra. - - Test scenario: - With ``style`` hidden from ``option_keys()`` (simulated old - cleopatra) but no ``style`` / ``hillshade`` passed, ``render_array`` - renders normally — the guard is scoped to preset use only. - """ - rng = np.random.default_rng(741) - arr = rng.random((5, 5)).astype("float32") - old_keys = ArrayGlyph.option_keys() - {"style", "hillshade"} - with patch.object(ArrayGlyph, "option_keys", return_value=old_keys): - glyph = render_array(arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot") - assert isinstance(glyph, ArrayGlyph) - @pytest.mark.skipif( not _supports_apply_style, reason="cleopatra < 0.25 has no glyph.apply_style()", diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index c43f3c693a..f93ac6a0b8 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -15,7 +15,6 @@ "cleopatra.glyphs.gridded.mesh_glyph", reason="cleopatra not installed" ) MeshGlyph = mesh_glyph.MeshGlyph -from pyramids.base._errors import OptionalPackageDoesNotExist from pyramids.netcdf.ugrid.dataset import UgridDataset from pyramids.netcdf.ugrid.plot import plot_mesh_data, plot_mesh_outline @@ -310,8 +309,12 @@ def test_style_and_hillshade_reach_mesh_glyph_plot(self): with patch.object(MeshGlyph, "plot") as mock_plot: ds.plot("depth", style="flow_accumulation", hillshade=True) kw = mock_plot.call_args.kwargs - assert kw.get("style") == "flow_accumulation" - assert kw.get("hillshade") is True + data_style = kw.get("data_style") + assert data_style is not None, ( + "style/hillshade must fold into a DataStyle on MeshGlyph.plot" + ) + assert data_style.style == "flow_accumulation" + assert data_style.hillshade is True def test_style_forwarded_to_mesh_render(self): """``UgridDataset.plot`` forwards ``style`` / ``hillshade`` to the helper.""" @@ -324,21 +327,14 @@ def test_style_forwarded_to_mesh_render(self): assert kw.get("style") == "topography" assert kw.get("hillshade") is True - def test_style_on_old_cleopatra_raises_upgrade_hint(self): - """``style=`` on a MeshGlyph lacking preset support raises the >= 0.24 hint.""" - ds = self._dataset("face") - old_keys = MeshGlyph.option_keys() - {"style", "hillshade"} - with patch.object(MeshGlyph, "option_keys", return_value=old_keys): - with pytest.raises(OptionalPackageDoesNotExist, match="cleopatra >= 0.24"): - ds.plot("depth", style="topography") - - def test_falsy_hillshade_not_guarded_on_old_cleopatra(self): - """``hillshade=False`` is dropped, so the mesh guard does not fire.""" + def test_falsy_hillshade_is_dropped(self): + """``hillshade=False`` is dropped — no ``DataStyle`` is built for it.""" ds = self._dataset("face") - old_keys = MeshGlyph.option_keys() - {"style", "hillshade"} - with patch.object(MeshGlyph, "option_keys", return_value=old_keys): - result = ds.plot("depth", hillshade=False) - assert isinstance(result, MeshGlyph) + with patch.object(MeshGlyph, "plot") as mock_plot: + ds.plot("depth", hillshade=False) + assert mock_plot.call_args.kwargs.get("data_style") is None, ( + "a falsy hillshade must not build a DataStyle" + ) @pytest.mark.skipif( not _mesh_supports_apply_style, From 1bbef63b9c46a0fb9f586d101c5225ba4182de00 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 19:16:54 +0200 Subject: [PATCH 07/30] refactor(plot): align UgridDataset.plot and FeatureCollection.plot with the raster family Give the mesh and vector plot facades the same explicit render-control surface as Dataset.plot / DatasetCollection.plot (colorbar / points / kind / title), forwarding what each backend supports and accepting the rest as documented no-ops: - UgridDataset.plot: hoist colorbar (forwarded to MeshGlyph.plot only when set, so the drawn-bar default survives) plus points / kind. A mesh has no point overlay and a fixed tripcolor/tricontour renderer, so points / kind are accepted for signature symmetry and ignored. - FeatureCollection.plot: hoist colorbar / points / kind / title across both engines. On engine="geopandas", colorbar toggles the geopandas legend and title is set on the returned Axes; on engine="cleopatra", colorbar / title now forward to the ScatterGlyph / PolygonGlyph plot() call (they were previously dropped by filter_kwargs, which only keeps constructor options). points / kind are vector no-ops. Tests: colorbar reaches / omitted-preserves-default / explicit-False on the mesh path; colorbar+title reach the glyph and points/kind are dropped on the cleopatra engine; title sets the Axes title and colorbar toggles the legend on the geopandas engine. feature + ugrid suites: 949 passed; mypy clean; doctests pass. --- src/pyramids/feature/_plot.py | 45 +++++++++++++-- src/pyramids/feature/collection.py | 35 ++++++++++- src/pyramids/netcdf/ugrid/dataset.py | 27 ++++++++- tests/feature/test_plot_cleopatra.py | 86 ++++++++++++++++++++++++++++ tests/ugrid/test_plot.py | 74 ++++++++++++++++++++++++ 5 files changed, 259 insertions(+), 8 deletions(-) diff --git a/src/pyramids/feature/_plot.py b/src/pyramids/feature/_plot.py index f077af20bf..d4a4d5c043 100644 --- a/src/pyramids/feature/_plot.py +++ b/src/pyramids/feature/_plot.py @@ -26,18 +26,35 @@ def plot( column: str | None = None, basemap: Any = None, engine: str = "geopandas", + colorbar: Any = None, + title: str | None = None, **kwargs: Any, ) -> Any: - """Render `fc` via geopandas or cleopatra, optionally over a web-tile basemap.""" + """Render `fc` via geopandas or cleopatra, optionally over a web-tile basemap. + + ``colorbar`` / ``title`` are the two raster-family plot params that map onto the + vector back-ends: on geopandas ``colorbar`` toggles the ``legend`` and ``title`` is + set on the returned Axes; on cleopatra both forward to the glyph's ``plot`` call. + Each is only applied when set, so an untouched call keeps the back-end default. + """ if engine == "geopandas": # geopandas' `.plot` is a CachedAccessor: `GeoDataFrame.plot` is the accessor # class, so construct it bound to `fc` and then call it. This reaches geopandas' # implementation while bypassing FeatureCollection's `plot` facade override — # equivalent to the original in-class `super().plot(...)`. - result = gpd.GeoDataFrame.plot(fc)(column=column, **kwargs) + gp_kwargs = dict(kwargs) + if colorbar is not None: + # geopandas has no ColorBar object; its `legend` draws the bar, so a + # ColorBar spec (or True) shows it and False hides it. + gp_kwargs["legend"] = bool(colorbar) + result = gpd.GeoDataFrame.plot(fc)(column=column, **gp_kwargs) ax = result + if title is not None: + ax.set_title(title) elif engine == "cleopatra": - result, ax = plot_cleopatra(fc, column=column, **kwargs) + result, ax = plot_cleopatra( + fc, column=column, colorbar=colorbar, title=title, **kwargs + ) else: raise ValueError( f"Unsupported engine {engine!r}; choose 'geopandas' or 'cleopatra'." @@ -50,8 +67,19 @@ def plot( return result -def plot_cleopatra(fc: Any, column: str | None = None, **kwargs: Any) -> Any: - """Build and draw the cleopatra glyph for `fc`, returning ``(glyph, ax)``.""" +def plot_cleopatra( + fc: Any, + column: str | None = None, + colorbar: Any = None, + title: str | None = None, + **kwargs: Any, +) -> Any: + """Build and draw the cleopatra glyph for `fc`, returning ``(glyph, ax)``. + + ``colorbar`` / ``title`` are forwarded to the glyph's ``plot`` call (both + ``ScatterGlyph.plot`` and ``PolygonGlyph.plot`` accept them), each only when set so + the glyph default is preserved otherwise. + """ require_cleopatra() if column is not None and column not in fc.columns: raise ValueError( @@ -74,7 +102,12 @@ def plot_cleopatra(fc: Any, column: str | None = None, **kwargs: Any) -> Any: "engine='cleopatra' supports single Point or Polygon/MultiPolygon geometries; got " f"{sorted(geom_types, key=str)} (MultiPoint is not supported)." ) - _fig, ax, _coll = glyph.plot() + plot_call: dict[str, Any] = {} + if colorbar is not None: + plot_call["colorbar"] = colorbar + if title is not None: + plot_call["title"] = title + _fig, ax, _coll = glyph.plot(**plot_call) return glyph, ax diff --git a/src/pyramids/feature/collection.py b/src/pyramids/feature/collection.py index 6b97eba39c..dd02402b0c 100644 --- a/src/pyramids/feature/collection.py +++ b/src/pyramids/feature/collection.py @@ -30,6 +30,9 @@ from typing import TYPE_CHECKING, Any, Literal, cast if TYPE_CHECKING: + from cleopatra.glyphs.gridded.array_glyph import PointOverlay + from cleopatra.styling.colorbar import ColorBar + from pyramids.dataset import Dataset from pyramids.feature._lazy_collection import LazyFeatureCollection @@ -2192,6 +2195,10 @@ def plot( column: str | None = None, basemap: bool | str | None = None, engine: str = "geopandas", + colorbar: bool | ColorBar | None = None, + points: np.ndarray | PointOverlay | None = None, + kind: str = "auto", + title: str | None = None, **kwargs: Any, ) -> Any: """Plot features, optionally on a web-tile basemap. @@ -2215,6 +2222,21 @@ def plot( renders a single flat colour. basemap: ``True`` for OpenStreetMap, or a provider name string. engine: ``"geopandas"`` (default) or ``"cleopatra"``. + colorbar (bool or ColorBar, optional): Colour-bar spec, part of the + shared plot signature. On ``engine="geopandas"`` it toggles the + geopandas ``legend`` (a ``ColorBar`` object counts as "show"); on + ``engine="cleopatra"`` a ``pyramids.plot.ColorBar(label=…, …)`` styles + the glyph's bar. ``None`` (default) leaves each back-end's default. + points (np.ndarray or PointOverlay, optional): Accepted for signature + symmetry with the raster plot family, but a **no-op here** — a vector + layer has no separate point overlay (the geometry is the data). + Ignored. + kind (str, optional): Accepted for signature symmetry with the raster + plot family, but a **no-op here** — ``kind`` selects a raster renderer + (``imshow``/``pcolormesh``), which has no vector equivalent. Ignored. + title (str, optional): Axes/glyph title. Set on the returned Axes + (``engine="geopandas"``) or forwarded to the glyph + (``engine="cleopatra"``). Default ``None``. **kwargs: Forwarded to the chosen back-end. For ``"cleopatra"`` they are filtered to the glyph's accepted options via ``filter_kwargs``. @@ -2256,7 +2278,18 @@ def plot( ``` """ - return _plot.plot(self, column=column, basemap=basemap, engine=engine, **kwargs) + # ``points`` / ``kind`` are part of the shared raster-family plot signature + # but have no meaning for vector geometry, so they are accepted and ignored. + # ``colorbar`` / ``title`` map onto both back-ends and are forwarded. + return _plot.plot( + self, + column=column, + basemap=basemap, + engine=engine, + colorbar=colorbar, + title=title, + **kwargs, + ) def _plot_cleopatra(self, column: str | None = None, **kwargs: Any): """Render via cleopatra ``PolygonGlyph``/``ScatterGlyph``. diff --git a/src/pyramids/netcdf/ugrid/dataset.py b/src/pyramids/netcdf/ugrid/dataset.py index 4cfe109480..195be6a453 100644 --- a/src/pyramids/netcdf/ugrid/dataset.py +++ b/src/pyramids/netcdf/ugrid/dataset.py @@ -9,11 +9,15 @@ from dataclasses import replace from pathlib import Path -from typing import Any, cast +from typing import TYPE_CHECKING, Any, cast import geopandas as gpd import numpy as np import shapely + +if TYPE_CHECKING: + from cleopatra.glyphs.gridded.array_glyph import PointOverlay + from cleopatra.styling.colorbar import ColorBar from osgeo import gdal from pyproj import CRS, Transformer from shapely.geometry import LineString, box @@ -819,6 +823,9 @@ def plot( cmap: str = "viridis", title: str | None = None, basemap: bool | str | None = None, + colorbar: bool | ColorBar | None = None, + points: np.ndarray | PointOverlay | None = None, + kind: str = "auto", **kwargs: Any, ) -> Any: """Plot a mesh data variable. @@ -838,6 +845,17 @@ def plot( basemap: If True, add an OpenStreetMap basemap. If a string, use it as the tile provider name (e.g. "CartoDB.Positron"). Default is None (no basemap). Requires the [viz] extra. + colorbar (bool or ColorBar, optional): Colour-bar spec, part of the + shared plot signature. A ``pyramids.plot.ColorBar(label=…, …)`` + configures the bar; ``False`` hides it and ``None`` (default) uses + cleopatra's default (a bar is drawn). Only forwarded when set. + points (np.ndarray or PointOverlay, optional): Accepted for signature + symmetry with the raster plot family, but a **no-op here** — a mesh + has no point-overlay layer (the mesh geometry is the data). Ignored. + kind (str, optional): Accepted for signature symmetry with the raster + plot family, but a **no-op here** — ``kind`` selects a raster + renderer (``imshow``/``pcolormesh``); the mesh always renders via + ``tripcolor``/``tricontour``. Ignored. **kwargs: Additional arguments passed to mesh_render (forwarded to plot_mesh_data). Notably ``colorbar`` (``bool``, default ``True``): pass ``colorbar=False`` to @@ -878,6 +896,13 @@ def plot( title = variable_name if basemap and self.epsg is None: raise ValueError("UgridDataset must have a CRS (epsg) to use basemap.") + # ``points`` / ``kind`` are part of the shared raster-family plot signature + # but have no meaning for a mesh (no point overlay; the renderer is fixed to + # tripcolor/tricontour), so they are accepted and ignored. Only ``colorbar`` + # maps onto the mesh backend, and only when explicitly set (so cleopatra's + # default drawn bar is preserved). + if colorbar is not None: + kwargs["colorbar"] = colorbar result = _mesh_render( mesh=self._mesh, data=data, diff --git a/tests/feature/test_plot_cleopatra.py b/tests/feature/test_plot_cleopatra.py index eaafe9012a..713a01f1ce 100644 --- a/tests/feature/test_plot_cleopatra.py +++ b/tests/feature/test_plot_cleopatra.py @@ -8,7 +8,10 @@ from __future__ import annotations +from unittest.mock import MagicMock, patch + import geopandas as gpd +import numpy as np import pytest from shapely.geometry import Point, Polygon, box @@ -217,3 +220,86 @@ def test_null_geometry_raises_clean_value_error(self, valid_geom): ) with pytest.raises(ValueError, match="Point or Polygon"): fc.plot(column="v", engine="cleopatra") + + +class TestFeatureCollectionPlotAlignment: + """The raster-family plot params (colorbar/points/kind/title) on both engines. + + ``FeatureCollection.plot`` shares the raster plot signature. ``colorbar`` / ``title`` + map onto both back-ends; ``points`` / ``kind`` have no vector meaning and are accepted + but ignored. + """ + + @staticmethod + def _points_fc(): + """A two-point FeatureCollection carrying a numeric column.""" + return FeatureCollection( + gpd.GeoDataFrame( + {"v": [1.0, 2.0]}, + geometry=[Point(0, 0), Point(1, 1)], + crs="EPSG:4326", + ) + ) + + def test_cleopatra_forwards_colorbar_and_title_to_glyph_plot(self): + """``colorbar`` / ``title`` reach the glyph's ``plot`` on the cleopatra engine. + + Test scenario: + The cleopatra engine calls ``ScatterGlyph.plot`` — spy on it and assert the + hoisted ``colorbar`` / ``title`` arrive there (they used to be dropped by + ``filter_kwargs`` because they are render-call, not constructor, params). + """ + from cleopatra.styling.colorbar import ColorBar + + bar = ColorBar(label="v") + with patch.object( + ScatterGlyph, "plot", return_value=(MagicMock(), MagicMock(), MagicMock()) + ) as mock_plot: + self._points_fc().plot( + column="v", engine="cleopatra", colorbar=bar, title="values" + ) + kw = mock_plot.call_args.kwargs + assert kw.get("colorbar") is bar, "colorbar must reach the glyph plot call" + assert kw.get("title") == "values", "title must reach the glyph plot call" + + def test_cleopatra_points_and_kind_are_noops(self): + """``points`` / ``kind`` are accepted for symmetry and never reach the glyph. + + Test scenario: + The raster-only ``points`` / ``kind`` must neither raise nor be forwarded to + ``ScatterGlyph.plot`` on the cleopatra engine. + """ + with patch.object( + ScatterGlyph, "plot", return_value=(MagicMock(), MagicMock(), MagicMock()) + ) as mock_plot: + self._points_fc().plot( + column="v", + engine="cleopatra", + points=np.array([[1.0, 0, 0]]), + kind="contourf", + ) + kw = mock_plot.call_args.kwargs + assert "points" not in kw, "points must not reach the glyph plot call" + assert "kind" not in kw, "kind must not reach the glyph plot call" + + def test_geopandas_title_sets_axes_title(self): + """``title`` is set on the returned Axes for the geopandas engine. + + Test scenario: + geopandas' ``plot`` takes no ``title`` kwarg, so the facade sets it on the + returned Axes; ``ax.get_title()`` must echo it. + """ + ax = self._points_fc().plot(column="v", title="my map") + assert ax.get_title() == "my map", f"unexpected axes title: {ax.get_title()!r}" + + def test_geopandas_colorbar_toggles_legend(self): + """``colorbar`` maps to geopandas' ``legend`` (a colour-bar Axes appears). + + Test scenario: + A continuous ``legend`` adds a dedicated colour-bar Axes, so + ``colorbar=True`` yields a figure with two Axes and ``colorbar=False`` one. + """ + ax_on = self._points_fc().plot(column="v", colorbar=True) + assert len(ax_on.figure.axes) == 2, "colorbar=True must draw a legend Axes" + ax_off = self._points_fc().plot(column="v", colorbar=False) + assert len(ax_off.figure.axes) == 1, "colorbar=False must draw no legend Axes" diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index f93ac6a0b8..537e74ed68 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -353,3 +353,77 @@ def test_returned_mesh_glyph_supports_apply_style(self): assert result.style == "flow_accumulation" result.apply_style("bathymetry") assert result.style == "bathymetry", "apply_style must restyle in place" + + +class TestUgridPlotAlignment: + """The raster-family plot params (colorbar/points/kind) on ``UgridDataset.plot``. + + ``UgridDataset.plot`` shares the raster plot signature; ``colorbar`` maps onto the + mesh backend while ``points`` / ``kind`` have no mesh meaning and are accepted but + ignored. + """ + + @staticmethod + def _dataset(location="face"): + """Build a single-face UgridDataset carrying a ``depth`` variable.""" + data = np.array([5.0]) if location == "face" else np.array([0.0, 1.0, 2.0]) + return UgridDataset.create_from_arrays( + node_x=np.array([0.0, 1.0, 0.5]), + node_y=np.array([0.0, 0.0, 1.0]), + face_node_connectivity=np.array([[0, 1, 2]]), + data={"depth": data}, + data_locations={"depth": location}, + ) + + def test_colorbar_spec_reaches_mesh_glyph_plot(self): + """An explicit ``colorbar`` forwards through to ``MeshGlyph.plot``. + + Test scenario: + ``UgridDataset.plot(colorbar=ColorBar(...))`` must forward the spec down + the facade -> mesh_render -> plot_mesh_data -> ``MeshGlyph.plot`` chain. + """ + from cleopatra.styling.colorbar import ColorBar + + bar = ColorBar(label="depth") + with patch.object(MeshGlyph, "plot") as mock_plot: + self._dataset("face").plot("depth", colorbar=bar) + assert mock_plot.call_args.kwargs.get("colorbar") is bar, ( + "an explicit colorbar must reach MeshGlyph.plot" + ) + + def test_default_colorbar_preserves_mesh_default(self): + """A default ``colorbar=None`` leaves the mesh back-end default in place. + + Test scenario: + Omitting ``colorbar`` must not override ``plot_mesh_data``'s default (which + draws the bar), so ``MeshGlyph.plot`` still receives the drawn-bar default + rather than a pyramids-injected ``None``. + """ + with patch.object(MeshGlyph, "plot") as mock_plot: + self._dataset("face").plot("depth") + assert mock_plot.call_args.kwargs.get("colorbar") is True, ( + "the mesh default (bar drawn) must survive an omitted colorbar" + ) + + def test_explicit_false_colorbar_reaches_mesh_glyph_plot(self): + """An explicit ``colorbar=False`` hides the bar via ``MeshGlyph.plot``.""" + with patch.object(MeshGlyph, "plot") as mock_plot: + self._dataset("face").plot("depth", colorbar=False) + assert mock_plot.call_args.kwargs.get("colorbar") is False, ( + "colorbar=False must reach MeshGlyph.plot" + ) + + def test_points_and_kind_are_accepted_noops(self): + """``points`` / ``kind`` are accepted for symmetry and ignored for a mesh. + + Test scenario: + Passing the raster-only ``points`` / ``kind`` must neither raise nor reach + ``MeshGlyph.plot`` (the mesh has no point overlay and a fixed renderer). + """ + with patch.object(MeshGlyph, "plot") as mock_plot: + self._dataset("face").plot( + "depth", points=np.array([[1.0, 0, 0]]), kind="contourf" + ) + kw = mock_plot.call_args.kwargs + assert "points" not in kw, "points must not reach MeshGlyph.plot" + assert "kind" not in kw, "kind must not reach MeshGlyph.plot" From 9a6fefed27bf2d3a94066770f4b4e708b39002b1 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 19:58:42 +0200 Subject: [PATCH 08/30] feat(plot): expose cleopatra's typed render groups on all .plot methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the colorbar / points wiring for the remaining cleopatra render-parameter dataclasses so every pyramids .plot method takes them as explicit, typed params (discoverable on the signature and accepted by mypy — they are not in the PlotKwargs/AnimateKwargs tails): - re-export ColorScaling / Contour / CellValues / DataStyle / Classify / PanelLabels through pyramids.plot (alongside ColorBar / PointOverlay / FrameLabel / Basemap). - Dataset.plot / DatasetCollection.plot / NetCDF.plot: color / contour / cells / data_style. UgridDataset.plot: color / contour / data_style (MeshGlyph has no cell-value overlay). FeatureCollection.plot: color / contour / classify, forwarded to the ScatterGlyph / PolygonGlyph plot() on engine="cleopatra" and ignored on engine="geopandas". - an explicitly-passed group wins over the one built from the loose kwargs: render_array and plot_mesh_data merge the loose-translated groups with setdefault, and the facades fold in only the set groups (nonnull_group_kwargs) so an unset group never blocks the translation or overrides a backend default. Tests: render_array precedence (explicit color wins over color_scale) + loose-still-builds + contour/cells/data_style reach; per-facade reach tests (Dataset via render_array, NetCDF, DatasetCollection, UgridDataset, FeatureCollection) and the geopandas-ignores-groups case; pyramids.plot re-export resolution for the new specs. Full plot surface: 1694 passed; mypy clean; doctests pass. --- src/pyramids/dataset/_plot_helpers.py | 33 ++++++- src/pyramids/dataset/collection.py | 31 ++++++- src/pyramids/dataset/dataset.py | 34 ++++++++ src/pyramids/feature/_plot.py | 38 +++++++-- src/pyramids/feature/collection.py | 24 +++++- src/pyramids/netcdf/netcdf.py | 39 +++++++-- src/pyramids/netcdf/ugrid/dataset.py | 27 +++++- src/pyramids/netcdf/ugrid/plot.py | 6 +- src/pyramids/plot.py | 29 +++++++ tests/dataset/collection/test_plot_labels.py | 27 ++++++ tests/dataset/plot/test_plot_engine.py | 85 +++++++++++++++++++ tests/dataset/plot/test_plot_specs.py | 22 ++++- tests/feature/test_plot_cleopatra.py | 55 ++++++++++++ .../netcdf/plot/test_hoisted_render_params.py | 34 ++++++++ tests/ugrid/test_plot.py | 44 ++++++++++ 15 files changed, 504 insertions(+), 24 deletions(-) diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index f2fa26c0e2..e299530d79 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -360,7 +360,9 @@ def _build_grouped_render_specs( if "num_size" in kwargs: cell_params["size"] = kwargs.pop("num_size") if "background_color_threshold" in kwargs: - cell_params["background_threshold"] = kwargs.pop("background_color_threshold") + cell_params["background_threshold"] = kwargs.pop( + "background_color_threshold" + ) specs["cells"] = CellValues(**cell_params) # style/hillshade -> DataStyle, honouring the historical no-op semantics: an explicit @@ -382,6 +384,27 @@ def _build_grouped_render_specs( return specs +def nonnull_group_kwargs(**groups: Any) -> dict[str, Any]: + """Return the cleopatra render-group kwargs that were actually set (not ``None``). + + Shared by the ``.plot`` facades: they expose the cleopatra render groups + (``color`` / ``contour`` / ``cells`` / ``data_style`` / ``classify``) as explicit + typed params and fold the set ones into the kwargs forwarded to the render backend. + Dropping the unset (``None``) groups keeps them off the render call, so an untouched + group never overrides the backend default and an explicitly-set one is routed straight + through (winning over the loose-kwarg translation — see :func:`render_array`). + + Args: + **groups: Candidate group objects keyed by their cleopatra render-method + parameter name (``color`` / ``contour`` / ``cells`` / ``data_style`` / + ``classify``). + + Returns: + A dict of only the groups whose value is not ``None``. + """ + return {name: value for name, value in groups.items() if value is not None} + + def render_array( *, arr: np.ndarray | None, @@ -743,8 +766,12 @@ def render_array( # The cleopatra group objects translated from pyramids' loose kwargs # (``color`` / ``contour`` / ``cells`` / ``data_style``) are render-method params on # ``plot`` / ``animate`` / ``facet``, not constructor options, so fold them into the - # render bucket (the animate merge below then picks them up too). - render_kwargs.update(group_specs) + # render bucket (the animate merge below then picks them up too). ``setdefault`` — not + # ``update`` — so an explicitly-passed group object (already routed to render_kwargs by + # the split above, e.g. ``color=ColorScaling(...)``) wins over the one built from the + # loose kwargs; the loose translation only fills a group the caller did not set. + for _spec_key, _spec_val in group_specs.items(): + render_kwargs.setdefault(_spec_key, _spec_val) # The ``"animate"`` path only flows kwargs into ``cleo.animate(...)``, # not the constructor — keys like ``interval`` are valid for animate # but not in cleopatra's ``DEFAULT_OPTIONS`` and would trigger an diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index f9cec8c93b..d74e74d30c 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -27,7 +27,7 @@ ) from pyramids.base.crs import crs_spec from pyramids.base.remote import cloud_config_from_env -from pyramids.dataset._plot_helpers import render_array +from pyramids.dataset._plot_helpers import nonnull_group_kwargs, render_array from pyramids.dataset._reduce_ops import resolve_dask_op from pyramids.dataset._stac import from_point as _from_point from pyramids.dataset._stac import from_stac as _from_stac @@ -54,6 +54,8 @@ PointOverlay, ) from cleopatra.styling.colorbar import ColorBar + from cleopatra.styling.params import CellValues, Contour, DataStyle + from cleopatra.styling.scaling import ColorScaling from dask.delayed import Delayed @@ -2436,6 +2438,10 @@ def plot( frame_label: FrameLabel | None = None, colorbar: bool | ColorBar | None = None, points: np.ndarray | PointOverlay | None = None, + color: ColorScaling | None = None, + contour: Contour | None = None, + cells: CellValues | None = None, + data_style: DataStyle | None = None, animation_axis_values: Any = None, **kwargs: Unpack[AnimateKwargs], ) -> ArrayGlyph: @@ -2530,6 +2536,21 @@ def plot( color=…, size=…, label_color=…, label_size=…)`` instead (on cleopatra >= 0.26 the loose ``point_color`` / ``point_size`` / ``pid_color`` / ``pid_size`` kwargs are deprecated). Default ``None``. + color (ColorScaling, optional): + Typed colour-scale spec ``pyramids.plot.ColorScaling`` — the typed + equivalent of the loose ``color_scale`` / ``gamma`` / ``bounds`` / + ``midpoint`` kwargs; the explicit spec wins. Default ``None``. + contour (Contour, optional): + Typed contour-line spec ``pyramids.plot.Contour`` — the typed equivalent + of the loose ``levels`` / ``labels`` / ``label_kw`` kwargs. Default + ``None``. + cells (CellValues, optional): + Typed per-cell annotation ``pyramids.plot.CellValues`` — the typed + equivalent of the loose ``display_cell_value`` / ``num_size`` / + ``background_color_threshold`` kwargs. Default ``None``. + data_style (DataStyle, optional): + Typed data-style / relief spec ``pyramids.plot.DataStyle`` — the typed + equivalent of the loose ``style`` / ``hillshade`` kwargs. Default ``None``. animation_axis_values (sequence, optional): Per-frame labels for the animation, one per timestep. Defaults to the collection's ``time`` axis when set (e.g. dates parsed by @@ -2663,6 +2684,14 @@ def plot( } if frame_label is not None: animate_extras["frame_label"] = frame_label + # Fold the explicitly-set cleopatra render groups in; unset ones are dropped so + # they neither override the backend default nor block the loose-kwarg translation + # in render_array (an explicit group still wins there). + animate_extras.update( + nonnull_group_kwargs( + color=color, contour=contour, cells=cells, data_style=data_style + ) + ) # Materialise the cube on demand for plotting. The render helper # expects a single (time, rows, cols) numpy array; reading each # Dataset's band into one stacked array is fine for a plot call diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index 84c13aa0bf..36d27f8da5 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -38,6 +38,7 @@ ) from pyramids.base.remote import cloud_config_from_env, redact_credentials from pyramids.dataset._ogc_coverages import from_ogc_coverages as _from_ogc_coverages +from pyramids.dataset._plot_helpers import nonnull_group_kwargs from pyramids.dataset._wcs import from_wcs as _from_wcs from pyramids.dataset._wms import from_wms as _from_wms from pyramids.dataset._wms import from_wmts as _from_wmts @@ -235,6 +236,8 @@ def _remap_nodata_to(arr: np.ndarray, src_nd: Any, dst_nd: Any) -> np.typing.NDA from cleopatra.basemap.geo import Basemap from cleopatra.glyphs.gridded.array_glyph import PlotKwargs, PointOverlay from cleopatra.styling.colorbar import ColorBar + from cleopatra.styling.params import CellValues, Contour, DataStyle + from cleopatra.styling.scaling import ColorScaling from geopandas import GeoDataFrame @@ -815,6 +818,10 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo points: np.ndarray | PointOverlay | None = None, kind: str = "auto", title: str | None = None, + color: ColorScaling | None = None, + contour: Contour | None = None, + cells: CellValues | None = None, + data_style: DataStyle | None = None, rgb_options: dict | None = None, **kwargs: Unpack[PlotKwargs], ): @@ -888,6 +895,25 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo ``"imshow"`` / ``"pcolormesh"`` / ``"contour"`` / ``"contourf"``. title (str, optional): Axes title. Default is ``None`` (cleopatra's default title). + color (ColorScaling, optional): + Typed colour-scale spec ``pyramids.plot.ColorScaling`` (linear / power / + sym-log / boundary / midpoint norm), e.g. + ``ColorScaling.power(gamma=0.7)``. The typed equivalent of the loose + ``color_scale`` / ``gamma`` / ``bounds`` / ``midpoint`` kwargs; when both + are given the explicit ``color`` wins. Default ``None``. + contour (Contour, optional): + Typed contour-line spec ``pyramids.plot.Contour(levels=…, labels=…, + label_kw=…)``. The typed equivalent of the loose ``levels`` / ``labels`` / + ``label_kw`` kwargs; the explicit spec wins. Default ``None``. + cells (CellValues, optional): + Typed per-cell value annotation ``pyramids.plot.CellValues(show=…, size=…, + background_threshold=…)``. The typed equivalent of the loose + ``display_cell_value`` / ``num_size`` / ``background_color_threshold`` + kwargs; the explicit spec wins. Default ``None``. + data_style (DataStyle, optional): + Typed data-style / relief spec ``pyramids.plot.DataStyle(style=…, + hillshade=…)``. The typed equivalent of the loose ``style`` / ``hillshade`` + kwargs; the explicit spec wins. Default ``None``. rgb_options (dict, optional): Grouped Sentinel-imagery kwargs. Accepted keys: ``"rgb"``, ``"surface_reflectance"``, ``"cutoff"``, @@ -981,6 +1007,13 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo percentile=percentile, ) resolved_band, resolved_rgb = self._resolve_plot_band(band, rgb) + # Spread the explicitly-set cleopatra render groups as their own ``**`` (not merged + # into the typed ``**kwargs``, whose PlotKwargs TypedDict has no group keys); the + # unset ones are dropped so they neither override the backend default nor block the + # loose-kwarg translation in render_array (an explicit group still wins there). + group_kwargs = nonnull_group_kwargs( + color=color, contour=contour, cells=cells, data_style=data_style + ) return self.analysis.plot( band=resolved_band, exclude_value=exclude_value, @@ -995,6 +1028,7 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo points=points, kind=kind, title=title, + **group_kwargs, **kwargs, ) diff --git a/src/pyramids/feature/_plot.py b/src/pyramids/feature/_plot.py index d4a4d5c043..2460187e0f 100644 --- a/src/pyramids/feature/_plot.py +++ b/src/pyramids/feature/_plot.py @@ -28,14 +28,19 @@ def plot( engine: str = "geopandas", colorbar: Any = None, title: str | None = None, + color: Any = None, + contour: Any = None, + classify: Any = None, **kwargs: Any, ) -> Any: """Render `fc` via geopandas or cleopatra, optionally over a web-tile basemap. - ``colorbar`` / ``title`` are the two raster-family plot params that map onto the - vector back-ends: on geopandas ``colorbar`` toggles the ``legend`` and ``title`` is - set on the returned Axes; on cleopatra both forward to the glyph's ``plot`` call. - Each is only applied when set, so an untouched call keeps the back-end default. + ``colorbar`` / ``title`` are the raster-family plot params that map onto both vector + back-ends: on geopandas ``colorbar`` toggles the ``legend`` and ``title`` is set on the + returned Axes; on cleopatra both forward to the glyph's ``plot`` call. The typed render + groups ``color`` / ``contour`` / ``classify`` are cleopatra-glyph concepts with no + geopandas equivalent, so they forward to the glyph on ``engine="cleopatra"`` and are + ignored on ``engine="geopandas"``. Each is only applied when set. """ if engine == "geopandas": # geopandas' `.plot` is a CachedAccessor: `GeoDataFrame.plot` is the accessor @@ -53,7 +58,14 @@ def plot( ax.set_title(title) elif engine == "cleopatra": result, ax = plot_cleopatra( - fc, column=column, colorbar=colorbar, title=title, **kwargs + fc, + column=column, + colorbar=colorbar, + title=title, + color=color, + contour=contour, + classify=classify, + **kwargs, ) else: raise ValueError( @@ -72,13 +84,17 @@ def plot_cleopatra( column: str | None = None, colorbar: Any = None, title: str | None = None, + color: Any = None, + contour: Any = None, + classify: Any = None, **kwargs: Any, ) -> Any: """Build and draw the cleopatra glyph for `fc`, returning ``(glyph, ax)``. - ``colorbar`` / ``title`` are forwarded to the glyph's ``plot`` call (both - ``ScatterGlyph.plot`` and ``PolygonGlyph.plot`` accept them), each only when set so - the glyph default is preserved otherwise. + ``colorbar`` / ``title`` and the typed render groups ``color`` / ``contour`` / + ``classify`` are forwarded to the glyph's ``plot`` call (both ``ScatterGlyph.plot`` and + ``PolygonGlyph.plot`` accept them), each only when set so the glyph default is + preserved otherwise. """ require_cleopatra() if column is not None and column not in fc.columns: @@ -107,6 +123,12 @@ def plot_cleopatra( plot_call["colorbar"] = colorbar if title is not None: plot_call["title"] = title + if color is not None: + plot_call["color"] = color + if contour is not None: + plot_call["contour"] = contour + if classify is not None: + plot_call["classify"] = classify _fig, ax, _coll = glyph.plot(**plot_call) return glyph, ax diff --git a/src/pyramids/feature/collection.py b/src/pyramids/feature/collection.py index dd02402b0c..c1fe31fa32 100644 --- a/src/pyramids/feature/collection.py +++ b/src/pyramids/feature/collection.py @@ -32,6 +32,8 @@ if TYPE_CHECKING: from cleopatra.glyphs.gridded.array_glyph import PointOverlay from cleopatra.styling.colorbar import ColorBar + from cleopatra.styling.params import Classify, Contour + from cleopatra.styling.scaling import ColorScaling from pyramids.dataset import Dataset from pyramids.feature._lazy_collection import LazyFeatureCollection @@ -2199,6 +2201,9 @@ def plot( points: np.ndarray | PointOverlay | None = None, kind: str = "auto", title: str | None = None, + color: ColorScaling | None = None, + contour: Contour | None = None, + classify: Classify | None = None, **kwargs: Any, ) -> Any: """Plot features, optionally on a web-tile basemap. @@ -2237,6 +2242,18 @@ def plot( title (str, optional): Axes/glyph title. Set on the returned Axes (``engine="geopandas"``) or forwarded to the glyph (``engine="cleopatra"``). Default ``None``. + color (ColorScaling, optional): Typed colour-scale spec + ``pyramids.plot.ColorScaling``, forwarded to the glyph on + ``engine="cleopatra"``. **Ignored on ``engine="geopandas"``** (no + geopandas equivalent). Default ``None``. + contour (Contour, optional): Typed contour-line spec + ``pyramids.plot.Contour``, forwarded to the glyph on + ``engine="cleopatra"``. Ignored on ``engine="geopandas"``. Default + ``None``. + classify (Classify, optional): Typed value-classification spec + ``pyramids.plot.Classify`` (``scheme`` / ``k``), forwarded to the glyph on + ``engine="cleopatra"``. Ignored on ``engine="geopandas"``. Default + ``None``. **kwargs: Forwarded to the chosen back-end. For ``"cleopatra"`` they are filtered to the glyph's accepted options via ``filter_kwargs``. @@ -2280,7 +2297,9 @@ def plot( """ # ``points`` / ``kind`` are part of the shared raster-family plot signature # but have no meaning for vector geometry, so they are accepted and ignored. - # ``colorbar`` / ``title`` map onto both back-ends and are forwarded. + # ``colorbar`` / ``title`` map onto both back-ends; ``color`` / ``contour`` / + # ``classify`` are cleopatra-glyph groups forwarded on that engine (and ignored on + # geopandas). All are forwarded and applied only when set. return _plot.plot( self, column=column, @@ -2288,6 +2307,9 @@ def plot( engine=engine, colorbar=colorbar, title=title, + color=color, + contour=contour, + classify=classify, **kwargs, ) diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index a44bdf1ea0..d7bc426d43 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -34,6 +34,7 @@ from pyramids.base.protocols import ArrayLike from pyramids.base.remote import is_remote from pyramids.dataset import Dataset +from pyramids.dataset._plot_helpers import nonnull_group_kwargs from pyramids.dataset.dataset import _COLLABORATOR_ATTRS from pyramids.feature import FeatureCollection from pyramids.netcdf._axis import detect_axis_indices @@ -74,6 +75,8 @@ from cleopatra.basemap.geo import Basemap from cleopatra.glyphs.gridded.array_glyph import PointOverlay from cleopatra.styling.colorbar import ColorBar + from cleopatra.styling.params import CellValues, Contour, DataStyle + from cleopatra.styling.scaling import ColorScaling # Guards the per-container `_lazy_managers` WeakSet against a concurrent lazy `read_array` (which adds) # and `close()` (which snapshots) on the same container from different threads. @@ -1654,6 +1657,10 @@ def plot( # type: ignore[override] figsize: tuple[float, float] | None = None, colorbar: bool | ColorBar | None = None, points: np.ndarray | PointOverlay | None = None, + color: ColorScaling | None = None, + contour: Contour | None = None, + cells: CellValues | None = None, + data_style: DataStyle | None = None, **kwargs: Any, ): """Plot a 2-D slice of a NetCDF variable using xarray-aligned vocabulary. @@ -1775,6 +1782,21 @@ def plot( # type: ignore[override] ``pyramids.plot.PointOverlay(...)`` to style them. Only forwarded when set — the multi-panel ``facet`` path has no ``points`` parameter and raises if one is supplied. Default is ``None``. + color (ColorScaling, optional): + Typed colour-scale spec ``pyramids.plot.ColorScaling`` — the typed + equivalent of the loose ``color_scale`` / ``gamma`` / ``bounds`` / + ``midpoint`` kwargs; the explicit spec wins. Default ``None``. + contour (Contour, optional): + Typed contour-line spec ``pyramids.plot.Contour`` — the typed equivalent + of the loose ``levels`` / ``labels`` / ``label_kw`` kwargs. Default + ``None``. + cells (CellValues, optional): + Typed per-cell annotation ``pyramids.plot.CellValues`` — the typed + equivalent of the loose ``display_cell_value`` / ``num_size`` / + ``background_color_threshold`` kwargs. Default ``None``. + data_style (DataStyle, optional): + Typed data-style / relief spec ``pyramids.plot.DataStyle`` — the typed + equivalent of the loose ``style`` / ``hillshade`` kwargs. Default ``None``. **kwargs: Additional keyword arguments forwarded to :meth:`Analysis.plot `. @@ -2131,15 +2153,22 @@ def plot( # type: ignore[override] ``` """ - # ``colorbar``/``points`` are hoisted to explicit params for parity with - # ``Dataset.plot`` / ``DatasetCollection.plot``; forward them through the - # same ``**kwargs`` channel the multi-mode render path already consumes. - # Only inject when set — the facet path rejects ``points`` outright, so a - # default ``None`` must not reach it. + # ``colorbar``/``points`` and the typed render groups (``color`` / ``contour`` / + # ``cells`` / ``data_style``) are hoisted to explicit params for parity with + # ``Dataset.plot`` / ``DatasetCollection.plot``; forward them through the same + # ``**kwargs`` channel the multi-mode render path already consumes. Only inject + # when set — the facet path rejects ``points`` outright, so a default ``None`` + # must not reach it, and an unset group must not block render_array's loose-kwarg + # translation (an explicit group still wins there). if colorbar is not None: kwargs["colorbar"] = colorbar if points is not None: kwargs["points"] = points + kwargs.update( + nonnull_group_kwargs( + color=color, contour=contour, cells=cells, data_style=data_style + ) + ) return NetCDFPlot(self).run( variable, selectors=selectors, diff --git a/src/pyramids/netcdf/ugrid/dataset.py b/src/pyramids/netcdf/ugrid/dataset.py index 195be6a453..b9958871e0 100644 --- a/src/pyramids/netcdf/ugrid/dataset.py +++ b/src/pyramids/netcdf/ugrid/dataset.py @@ -18,6 +18,8 @@ if TYPE_CHECKING: from cleopatra.glyphs.gridded.array_glyph import PointOverlay from cleopatra.styling.colorbar import ColorBar + from cleopatra.styling.params import Contour, DataStyle + from cleopatra.styling.scaling import ColorScaling from osgeo import gdal from pyproj import CRS, Transformer from shapely.geometry import LineString, box @@ -25,6 +27,7 @@ from pyramids.base.crs import sr_from_epsg from pyramids.dataset import Dataset from pyramids.dataset._plot_helpers import mesh_render as _mesh_render +from pyramids.dataset._plot_helpers import nonnull_group_kwargs as _nonnull_group_kwargs from pyramids.feature import FeatureCollection from pyramids.netcdf._mdim import open_mdarray from pyramids.netcdf.cf import write_global_attributes @@ -826,6 +829,9 @@ def plot( colorbar: bool | ColorBar | None = None, points: np.ndarray | PointOverlay | None = None, kind: str = "auto", + color: ColorScaling | None = None, + contour: Contour | None = None, + data_style: DataStyle | None = None, **kwargs: Any, ) -> Any: """Plot a mesh data variable. @@ -856,6 +862,17 @@ def plot( plot family, but a **no-op here** — ``kind`` selects a raster renderer (``imshow``/``pcolormesh``); the mesh always renders via ``tripcolor``/``tricontour``. Ignored. + color (ColorScaling, optional): Typed colour-scale spec + ``pyramids.plot.ColorScaling`` — the typed equivalent of the loose + ``color_scale`` / ``gamma`` / ``bounds`` / ``midpoint`` kwargs; the + explicit spec wins. Default ``None``. + contour (Contour, optional): Typed contour-line spec + ``pyramids.plot.Contour`` — the typed equivalent of the loose ``levels`` + / ``label_kw`` kwargs. Default ``None``. + data_style (DataStyle, optional): Typed data-style / relief spec + ``pyramids.plot.DataStyle`` — the typed equivalent of the loose ``style`` + / ``hillshade`` kwargs. (A mesh has no cell-value overlay, so there is no + ``cells`` param here.) Default ``None``. **kwargs: Additional arguments passed to mesh_render (forwarded to plot_mesh_data). Notably ``colorbar`` (``bool``, default ``True``): pass ``colorbar=False`` to @@ -898,11 +915,15 @@ def plot( raise ValueError("UgridDataset must have a CRS (epsg) to use basemap.") # ``points`` / ``kind`` are part of the shared raster-family plot signature # but have no meaning for a mesh (no point overlay; the renderer is fixed to - # tripcolor/tricontour), so they are accepted and ignored. Only ``colorbar`` - # maps onto the mesh backend, and only when explicitly set (so cleopatra's - # default drawn bar is preserved). + # tripcolor/tricontour), so they are accepted and ignored. ``colorbar`` and the + # typed render groups (``color`` / ``contour`` / ``data_style``) map onto the mesh + # backend and are forwarded only when set (so cleopatra's defaults and the + # loose-kwarg translation are preserved otherwise). if colorbar is not None: kwargs["colorbar"] = colorbar + kwargs.update( + _nonnull_group_kwargs(color=color, contour=contour, data_style=data_style) + ) result = _mesh_render( mesh=self._mesh, data=data, diff --git a/src/pyramids/netcdf/ugrid/plot.py b/src/pyramids/netcdf/ugrid/plot.py index fb66889e78..ec279018a0 100644 --- a/src/pyramids/netcdf/ugrid/plot.py +++ b/src/pyramids/netcdf/ugrid/plot.py @@ -160,7 +160,12 @@ def plot_mesh_data( # objects; translate pyramids' loose color_scale / gamma / bounds / midpoint / levels / # style / hillshade into cleopatra's ColorScaling / Contour / DataStyle groups. MeshGlyph # has no cell-value overlay, so the cell-value keys are left untouched (include_cells). + # ``setdefault`` — so an explicitly-passed group object already in plot_kwargs (e.g. + # ``color=ColorScaling(...)`` from UgridDataset.plot) wins over the loose translation + # and the two never collide on the same keyword. group_specs = _build_grouped_render_specs(plot_kwargs, include_cells=False) + for _spec_key, _spec_val in group_specs.items(): + plot_kwargs.setdefault(_spec_key, _spec_val) glyph.plot( data, @@ -169,7 +174,6 @@ def plot_mesh_data( edgecolor=edgecolor, colorbar=colorbar, title=title, - **group_specs, **plot_kwargs, ) return glyph diff --git a/src/pyramids/plot.py b/src/pyramids/plot.py index 4198d3bec7..3f485c2e72 100644 --- a/src/pyramids/plot.py +++ b/src/pyramids/plot.py @@ -27,6 +27,19 @@ - ``PointOverlay`` — styled point overlay, passed as ``points=``. - ``Basemap`` / ``Feature`` — shaded-relief / coastline reference layers, passed as ``basemap=`` (distinct from a web-tile ``basemap=""``). +- ``ColorScaling`` — the colour-scale spec (linear / power / sym-log / boundary / + midpoint norm + colour-bar construction), passed as ``color=``. Its variant + constructors are ``ColorScaling.power(gamma=...)`` etc. +- ``Contour`` — contour-line control (``levels`` / ``labels`` / ``label_kw``), + passed as ``contour=``. +- ``CellValues`` — per-cell value annotation (``show`` / ``size`` / + ``background_threshold``), passed as ``cells=``. +- ``DataStyle`` — data-style preset + relief (``style`` / ``hillshade`` / ``bands`` / + ``alpha``), passed as ``data_style=``. +- ``Classify`` — value classification for vector glyphs (``scheme`` / ``k``), passed + as ``classify=``. +- ``PanelLabels`` — per-panel coordinate labels for faceting (``col`` / ``row``), + passed as ``labels=`` on the facet path. """ from typing import TYPE_CHECKING, Any @@ -40,20 +53,36 @@ from cleopatra.basemap.geo import Basemap, Feature # noqa: F401 from cleopatra.glyphs.gridded.array_glyph import ( # noqa: F401 FrameLabel, + PanelLabels, PointOverlay, ) from cleopatra.styling.colorbar import ColorBar # noqa: F401 + from cleopatra.styling.params import ( # noqa: F401 + CellValues, + Classify, + Contour, + DataStyle, + ) + from cleopatra.styling.scaling import ColorScaling # noqa: F401 _ARRAY_GLYPH = "cleopatra.glyphs.gridded.array_glyph" _COLORBAR = "cleopatra.styling.colorbar" _GEO = "cleopatra.basemap.geo" +_PARAMS = "cleopatra.styling.params" +_SCALING = "cleopatra.styling.scaling" _CLEO_EXPORTS: dict[str, tuple[str, str]] = { "ColorBar": (_COLORBAR, "ColorBar"), "FrameLabel": (_ARRAY_GLYPH, "FrameLabel"), "PointOverlay": (_ARRAY_GLYPH, "PointOverlay"), + "PanelLabels": (_ARRAY_GLYPH, "PanelLabels"), "Basemap": (_GEO, "Basemap"), "Feature": (_GEO, "Feature"), + "ColorScaling": (_SCALING, "ColorScaling"), + "Contour": (_PARAMS, "Contour"), + "CellValues": (_PARAMS, "CellValues"), + "DataStyle": (_PARAMS, "DataStyle"), + "Classify": (_PARAMS, "Classify"), } __all__ = sorted(_CLEO_EXPORTS) diff --git a/tests/dataset/collection/test_plot_labels.py b/tests/dataset/collection/test_plot_labels.py index b83ffaa548..ddc5d46b76 100644 --- a/tests/dataset/collection/test_plot_labels.py +++ b/tests/dataset/collection/test_plot_labels.py @@ -259,3 +259,30 @@ def test_setter_length_validation_and_clear(self): cube.time = [2000, 2001] cube.time = None assert cube.time is None + + +class TestPlotRenderGroups: + """The typed render groups are forwarded from ``DatasetCollection.plot``. + + ``render_array`` is mocked, so plain sentinels stand in for the cleopatra group + objects — the test only pins that a set group is forwarded and an unset one is not. + """ + + def test_set_groups_are_forwarded(self): + """Explicit ``color`` / ``data_style`` reach ``render_array``.""" + cube = _collection(3) + color, data_style = object(), object() + with patch("pyramids.dataset.collection.render_array") as render: + cube.plot(band=0, color=color, data_style=data_style) + kw = render.call_args.kwargs + assert kw["color"] is color, "explicit color must be forwarded" + assert kw["data_style"] is data_style, "explicit data_style must be forwarded" + + def test_unset_groups_are_not_forwarded(self): + """Unset groups are dropped so they never override the render backend default.""" + cube = _collection(3) + with patch("pyramids.dataset.collection.render_array") as render: + cube.plot(band=0) + kw = render.call_args.kwargs + for name in ("color", "contour", "cells", "data_style"): + assert name not in kw, f"unset {name} must not be forwarded" diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index 6b3b2c29f9..d69d5500e6 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -956,3 +956,88 @@ def test_frame_label_animates(self): frame_label=FrameLabel(location=(1, 3)), ) assert isinstance(glyph, ArrayGlyph) + + +class TestRenderArrayGroupParams: + """Explicit cleopatra render-group objects and their precedence in render_array. + + The typed groups (``color`` / ``contour`` / ``cells`` / ``data_style``) reach the + render call, and an explicitly-passed group wins over the one built from the loose + kwargs (``color_scale`` / ``style`` / ...). + """ + + def test_explicit_color_group_wins_over_loose_color_scale(self): + """An explicit ``color=ColorScaling`` overrides a loose ``color_scale``. + + Test scenario: + Passing both the typed ``color`` and the loose ``color_scale`` must forward + the explicit spec (the loose translation only fills an unset group). + """ + from pyramids.plot import ColorScaling + + fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() + rng = np.random.default_rng(11) + arr = rng.random((4, 4)).astype("float32") + explicit = ColorScaling.power(gamma=0.9) + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): + render_array( + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + color=explicit, + color_scale="linear", + ) + assert plot.get("color") is explicit, "explicit color must win over color_scale" + + def test_loose_kwargs_build_group_when_no_explicit(self): + """Loose ``color_scale`` / ``style`` still build the groups when unset explicitly. + + Test scenario: + With no explicit ``color`` / ``data_style``, the loose kwargs must translate + into ``ColorScaling`` / ``DataStyle`` on the render call (backward compat). + """ + from cleopatra.styling.params import DataStyle + from cleopatra.styling.scaling import ColorScaling + + fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() + rng = np.random.default_rng(12) + arr = rng.random((4, 4)).astype("float32") + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): + render_array( + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + color_scale="power", + gamma=0.5, + style="flow_accumulation", + ) + assert isinstance(plot.get("color"), ColorScaling), "color_scale must build color" + assert isinstance(plot.get("data_style"), DataStyle), "style must build data_style" + + def test_explicit_groups_reach_render_call(self): + """Explicit ``contour`` / ``cells`` / ``data_style`` reach the render call. + + Test scenario: + The typed groups other than ``color`` also forward verbatim to + ``ArrayGlyph.plot``. + """ + from cleopatra.styling.params import CellValues, Contour, DataStyle + + fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() + rng = np.random.default_rng(13) + arr = rng.random((4, 4)).astype("float32") + contour, cells, data_style = Contour(levels=4), CellValues(show=True), DataStyle( + style="flow_accumulation" + ) + with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): + render_array( + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + contour=contour, + cells=cells, + data_style=data_style, + ) + assert plot.get("contour") is contour, "explicit contour must reach the render call" + assert plot.get("cells") is cells, "explicit cells must reach the render call" + assert plot.get("data_style") is data_style, "explicit data_style must reach it" diff --git a/tests/dataset/plot/test_plot_specs.py b/tests/dataset/plot/test_plot_specs.py index 773cacd2f5..efff002c14 100644 --- a/tests/dataset/plot/test_plot_specs.py +++ b/tests/dataset/plot/test_plot_specs.py @@ -18,8 +18,20 @@ class TestPlotSpecReExports: """`pyramids.plot` lazily re-exports cleopatra's plot spec classes.""" def test_all_and_dir_list_the_specs(self): - """`__all__` / `dir()` advertise the five specs, sorted.""" - expected = ["Basemap", "ColorBar", "Feature", "FrameLabel", "PointOverlay"] + """`__all__` / `dir()` advertise every re-exported spec, sorted.""" + expected = [ + "Basemap", + "CellValues", + "Classify", + "ColorBar", + "ColorScaling", + "Contour", + "DataStyle", + "Feature", + "FrameLabel", + "PanelLabels", + "PointOverlay", + ] assert plot_specs.__all__ == expected assert dir(plot_specs) == expected @@ -29,8 +41,14 @@ def test_all_and_dir_list_the_specs(self): ("ColorBar", "cleopatra.styling.colorbar", "ColorBar"), ("FrameLabel", "cleopatra.glyphs.gridded.array_glyph", "FrameLabel"), ("PointOverlay", "cleopatra.glyphs.gridded.array_glyph", "PointOverlay"), + ("PanelLabels", "cleopatra.glyphs.gridded.array_glyph", "PanelLabels"), ("Basemap", "cleopatra.basemap.geo", "Basemap"), ("Feature", "cleopatra.basemap.geo", "Feature"), + ("ColorScaling", "cleopatra.styling.scaling", "ColorScaling"), + ("Contour", "cleopatra.styling.params", "Contour"), + ("CellValues", "cleopatra.styling.params", "CellValues"), + ("DataStyle", "cleopatra.styling.params", "DataStyle"), + ("Classify", "cleopatra.styling.params", "Classify"), ], ) def test_spec_resolves_to_cleopatra_class(self, name, module_name, attribute): diff --git a/tests/feature/test_plot_cleopatra.py b/tests/feature/test_plot_cleopatra.py index 713a01f1ce..b072d35c7e 100644 --- a/tests/feature/test_plot_cleopatra.py +++ b/tests/feature/test_plot_cleopatra.py @@ -303,3 +303,58 @@ def test_geopandas_colorbar_toggles_legend(self): assert len(ax_on.figure.axes) == 2, "colorbar=True must draw a legend Axes" ax_off = self._points_fc().plot(column="v", colorbar=False) assert len(ax_off.figure.axes) == 1, "colorbar=False must draw no legend Axes" + + +class TestFeatureCollectionGroupParams: + """Typed render groups (color/contour/classify) on ``FeatureCollection.plot``. + + ``ScatterGlyph`` / ``PolygonGlyph`` accept ``color`` / ``contour`` / ``classify``; the + facade forwards them on ``engine="cleopatra"`` and ignores them on + ``engine="geopandas"`` (no geopandas equivalent). + """ + + @staticmethod + def _points_fc(): + """A two-point FeatureCollection carrying a numeric column.""" + return FeatureCollection( + gpd.GeoDataFrame( + {"v": [1.0, 2.0]}, + geometry=[Point(0, 0), Point(1, 1)], + crs="EPSG:4326", + ) + ) + + def test_cleopatra_forwards_color_contour_classify(self): + """``color`` / ``contour`` / ``classify`` reach the glyph on the cleopatra engine.""" + from cleopatra.styling.params import Classify, Contour + from cleopatra.styling.scaling import ColorScaling + + color = ColorScaling.linear() + contour = Contour(levels=3) + classify = Classify(scheme="quantiles", k=3) + with patch.object( + ScatterGlyph, "plot", return_value=(MagicMock(), MagicMock(), MagicMock()) + ) as mock_plot: + self._points_fc().plot( + column="v", + engine="cleopatra", + color=color, + contour=contour, + classify=classify, + ) + kw = mock_plot.call_args.kwargs + assert kw.get("color") is color, "color must reach the glyph plot call" + assert kw.get("contour") is contour, "contour must reach the glyph plot call" + assert kw.get("classify") is classify, "classify must reach the glyph plot call" + + def test_geopandas_ignores_group_objects(self): + """A cleopatra group on the geopandas engine is ignored, not forwarded/erroring. + + Test scenario: + geopandas has no ``color`` kwarg, so passing a ``ColorScaling`` on the default + engine must render a plain Axes rather than raising an unexpected-kwarg error. + """ + from cleopatra.styling.scaling import ColorScaling + + result = self._points_fc().plot(column="v", color=ColorScaling.power(gamma=0.7)) + assert isinstance(result, Axes), f"expected Axes, got {type(result)}" diff --git a/tests/netcdf/plot/test_hoisted_render_params.py b/tests/netcdf/plot/test_hoisted_render_params.py index b4905b6516..8f7a150331 100644 --- a/tests/netcdf/plot/test_hoisted_render_params.py +++ b/tests/netcdf/plot/test_hoisted_render_params.py @@ -172,3 +172,37 @@ def test_facet_with_explicit_points_raises(self): with pytest.raises(ValueError, match="points") as exc_info: nc.plot(variable="t2m", facet=FacetSpec(col="time"), points=_points()) assert "points" in str(exc_info.value), f"unexpected error: {exc_info.value}" + + +class TestNetCDFGroupParams: + """Typed render groups (color/contour/cells/data_style) on ``NetCDF.plot``.""" + + def test_group_reaches_the_render_call(self): + """An explicit ``color`` forwards through to ``Analysis.plot``. + + Test scenario: + ``nc.plot(variable="t2m", color=ColorScaling(...))`` forwards the spec down + the facade -> NetCDFPlot.run -> Analysis.plot chain. + """ + from cleopatra.styling.scaling import ColorScaling + + nc = make_plot_3d_nc() + var = nc.get_variable("t2m") + color = ColorScaling.power(gamma=0.8) + with patch.object(type(var.analysis), "plot", autospec=True) as mock_plot: + mock_plot.return_value = "ok" + nc.plot(variable="t2m", color=color) + assert mock_plot.call_args.kwargs.get("color") is color, ( + "explicit color must reach Analysis.plot" + ) + + def test_unset_groups_not_forwarded(self): + """A bare plot forwards none of the typed render groups.""" + nc = make_plot_3d_nc() + var = nc.get_variable("t2m") + with patch.object(type(var.analysis), "plot", autospec=True) as mock_plot: + mock_plot.return_value = "ok" + nc.plot(variable="t2m") + forwarded = mock_plot.call_args.kwargs + for name in ("color", "contour", "cells", "data_style"): + assert name not in forwarded, f"unset {name} must not be forwarded" diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index 537e74ed68..a69e872c3b 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -427,3 +427,47 @@ def test_points_and_kind_are_accepted_noops(self): kw = mock_plot.call_args.kwargs assert "points" not in kw, "points must not reach MeshGlyph.plot" assert "kind" not in kw, "kind must not reach MeshGlyph.plot" + + +class TestUgridGroupParams: + """Typed render groups (color/contour/data_style) on ``UgridDataset.plot``. + + ``MeshGlyph.plot`` accepts ``color`` / ``contour`` / ``data_style`` (no ``cells``); + the facade forwards them when set, and an explicit group wins over the loose kwargs. + """ + + @staticmethod + def _dataset(location="face"): + """Build a single-face UgridDataset carrying a ``depth`` variable.""" + data = np.array([5.0]) if location == "face" else np.array([0.0, 1.0, 2.0]) + return UgridDataset.create_from_arrays( + node_x=np.array([0.0, 1.0, 0.5]), + node_y=np.array([0.0, 0.0, 1.0]), + face_node_connectivity=np.array([[0, 1, 2]]), + data={"depth": data}, + data_locations={"depth": location}, + ) + + def test_color_and_data_style_reach_mesh_glyph_plot(self): + """Explicit ``color`` / ``data_style`` forward to ``MeshGlyph.plot``.""" + from cleopatra.styling.params import DataStyle + from cleopatra.styling.scaling import ColorScaling + + color = ColorScaling.power(gamma=0.8) + data_style = DataStyle(style="flow_accumulation") + with patch.object(MeshGlyph, "plot") as mock_plot: + self._dataset("face").plot("depth", color=color, data_style=data_style) + kw = mock_plot.call_args.kwargs + assert kw.get("color") is color, "explicit color must reach MeshGlyph.plot" + assert kw.get("data_style") is data_style, "explicit data_style must reach it" + + def test_explicit_color_wins_over_loose_color_scale(self): + """An explicit ``color`` overrides the loose ``color_scale`` on the mesh path.""" + from cleopatra.styling.scaling import ColorScaling + + color = ColorScaling.power(gamma=0.6) + with patch.object(MeshGlyph, "plot") as mock_plot: + self._dataset("face").plot("depth", color=color, color_scale="linear") + assert mock_plot.call_args.kwargs.get("color") is color, ( + "explicit color must win over loose color_scale" + ) From 49afb2e7fef17604dea31ad516e6e636caf361e2 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 21:00:45 +0200 Subject: [PATCH 09/30] refactor(plot)!: adopt cleopatra 0.30.0 and remove the loose styling kwargs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cleopatra 0.30.0 is released; switch pyproject off the git pin to the published `cleopatra[tiles]>=0.30.0`. With the grouped render parameters now the upstream API, drop pyramids' backward-compat translation shims so the loose styling kwargs are no longer accepted — the typed group objects are the only way. - remove _build_grouped_render_specs and _migrate_deprecated_plot_specs. The loose forms cleopatra removed (color_scale / gamma / bounds / midpoint / line_* / levels / label_kw / display_cell_value / num_size / background_color_threshold / style / hillshade / point_* / pid_*) now raise cleopatra's "moved onto a grouped parameter object" error. - reject the loose cbar_* / ticks_spacing colour-bar kwargs in pyramids (cleopatra still tolerates them, but ColorBar is the single typed colour-bar surface) with a pointer to colorbar=ColorBar(...). cbar_kwargs (raw matplotlib dict) and add_colorbar stay valid. - rewire the NetCDF ColorOpts bag to emit contour=Contour(levels=) and data_style=DataStyle(style=, hillshade=) instead of loose kwargs. - migrate the paletted-raster path (#913) to color=ColorScaling.boundary(bounds=). - refresh every plot docstring that referenced the removed loose forms. BREAKING CHANGE: the loose plot styling keywords (color_scale, gamma, bounds, midpoint, line_threshold, line_scale, levels, label_kw, display_cell_value, num_size, background_color_threshold, style, hillshade, point_color/point_size/point_label_*/pid_*, cbar_label/cbar_length/cbar_orientation/cbar_label_*/cbar_location/cbar_inside/cbar_box/ cbar_tick_color/ticks_spacing) are removed; pass the typed group objects instead (color=ColorScaling, contour=Contour, cells=CellValues, data_style=DataStyle, points=PointOverlay, colorbar=ColorBar). Full plot surface: 1676 passed; mypy clean; doctests pass. --- pixi.lock | 119 +++---- pyproject.toml | 11 +- src/pyramids/dataset/_plot_helpers.py | 307 +++-------------- src/pyramids/dataset/abstract_dataset.py | 6 +- src/pyramids/dataset/collection.py | 37 +- src/pyramids/dataset/dataset.py | 24 +- src/pyramids/dataset/engines/analysis.py | 84 +++-- src/pyramids/dataset/engines/vectorize.py | 9 +- src/pyramids/netcdf/_plot.py | 17 +- src/pyramids/netcdf/netcdf.py | 20 +- src/pyramids/netcdf/ugrid/dataset.py | 19 +- src/pyramids/netcdf/ugrid/plot.py | 18 +- tests/dataset/plot/test_new_render_params.py | 89 +---- tests/dataset/plot/test_plot_color.py | 14 +- tests/dataset/plot/test_plot_dataset.py | 13 +- tests/dataset/plot/test_plot_engine.py | 344 ++++--------------- tests/dataset/plot/test_plot_facade.py | 44 --- tests/netcdf/plot/test_plot_options.py | 29 +- tests/netcdf/plot/test_plot_render.py | 9 +- tests/ugrid/test_plot.py | 41 ++- 20 files changed, 365 insertions(+), 889 deletions(-) diff --git a/pixi.lock b/pixi.lock index ddf7e63896..5231b7fb45 100644 --- a/pixi.lock +++ b/pixi.lock @@ -137,13 +137,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/05/1e/ada6fb15a1d75b5bd9b554355a69a798c55a7dcc93b8d41596265c1772e3/pyproj-3.7.2-cp314-cp314-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/2d/43c8522a2038e9d0e7dbdf3a61195ecc31ca576fb1527a528c877e87d973/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl @@ -253,10 +253,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/33/e7/1925bfbc563c39c1d2e82501d8372734a5c725e53ac3b31b4c2d081e895b/imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/81/e4/4c50ceca7d0e937977866b02cb64e6ccf4df979a5871e521f9e255df6073/pyproj-3.7.2-cp314-cp314-manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl @@ -363,11 +363,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/42/17/c2b050d3f5b71b6edd0d96ae16c990fdc42a5f1366464a5c2772146de33a/pyproj-3.7.2-cp314-cp314-macosx_13_0_x86_64.whl - pypi: https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl @@ -473,13 +473,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/04/90/67bd7260b4ea9b8b20b4f58afef6c223ecb3abf368eb4ec5bc2cdef81b49/pyproj-3.7.2.tar.gz - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl - pypi: https://files.pythonhosted.org/packages/40/5c/f3d8a657d362cc93b81aab8feda487317da5b5d31c0e1fdfd5e986e55d17/imageio_ffmpeg-0.6.0-py3-none-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl @@ -577,10 +577,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/c6/fa760e12a2483469e2bf5058c5faff664acf66cadb4df2ad6205b016a73d/imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/a8/a9/d23012099dc88ec69a29c6407b41d89681cb674c2043cd5b467c7e299c08/xyzservices-2026.3.0-py3-none-any.whl @@ -803,7 +803,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -870,6 +869,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1167,7 +1167,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1230,6 +1229,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1525,7 +1525,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1589,6 +1588,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1881,7 +1881,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -1946,6 +1945,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -2232,7 +2232,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -2295,6 +2294,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -2596,7 +2596,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -2661,6 +2660,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/c6/76ee9dacedcd8c67d8fa53dd975613733bdd28242a4c41518ff1c8aeaa64/jupytext-1.19.5-py3-none-any.whl @@ -2951,7 +2951,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3013,6 +3012,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/c6/76ee9dacedcd8c67d8fa53dd975613733bdd28242a4c41518ff1c8aeaa64/jupytext-1.19.5-py3-none-any.whl @@ -3300,7 +3300,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3363,6 +3362,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -3650,7 +3650,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -3714,6 +3713,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/c6/76ee9dacedcd8c67d8fa53dd975613733bdd28242a4c41518ff1c8aeaa64/jupytext-1.19.5-py3-none-any.whl @@ -3995,7 +3995,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl @@ -4056,6 +4055,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/76/8e/56ccb09c7232a55403a7637caa21922f3b65901a37f5e8bdb405d0de0946/mike-2.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/c6/76ee9dacedcd8c67d8fa53dd975613733bdd28242a4c41518ff1c8aeaa64/jupytext-1.19.5-py3-none-any.whl @@ -4307,7 +4307,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4372,6 +4371,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -4615,7 +4615,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4676,6 +4675,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -4917,7 +4917,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -4979,6 +4978,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -5217,7 +5217,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5280,6 +5279,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -5514,7 +5514,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5575,6 +5574,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -5868,7 +5868,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -5934,6 +5933,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -6222,7 +6222,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6284,6 +6283,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -6570,7 +6570,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6633,6 +6632,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -6916,7 +6916,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -6980,6 +6979,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -7257,7 +7257,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7319,6 +7318,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -7559,7 +7559,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7629,6 +7628,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -7868,7 +7868,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -7935,6 +7934,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -8170,7 +8170,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8237,6 +8236,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -8470,7 +8470,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8541,6 +8540,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -8766,7 +8766,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -8833,6 +8832,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/e0/b95584605cec9ed50b7ebaf7975d1c4ddeec5a86b7a20554ed8b60042bd7/pyproj-3.7.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl @@ -9077,7 +9077,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl @@ -9145,6 +9144,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -9384,7 +9384,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9453,6 +9452,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -9684,7 +9684,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -9751,6 +9750,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -9982,7 +9982,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10047,6 +10046,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -10276,7 +10276,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10342,6 +10341,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/77/1d/c84e30c0c674184948b66f076ab271c01d940618a2824c23cd035a27bc20/debugpy-1.8.21-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -10585,7 +10585,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10652,6 +10651,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -10892,7 +10892,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -10961,6 +10960,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -11193,7 +11193,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11259,6 +11258,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -11492,7 +11492,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11559,6 +11558,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -11787,7 +11787,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -11851,6 +11850,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -12098,7 +12098,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12166,6 +12165,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -12407,7 +12407,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/zict-3.0.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12472,6 +12471,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -12710,7 +12710,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/zlib-1.3.2-hbb4bfdb_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/zstd-1.5.7-h3eecb57_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -12776,6 +12775,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -13011,7 +13011,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-1.3.2-h8088a28_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -13079,6 +13078,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -13308,7 +13308,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: ./ - - pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl @@ -13374,6 +13373,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/77/e4/288365afae98953bc01de09f686f40d8ee84578135aa7767d5d4e60b5278/mistune-3.3.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -36468,7 +36468,7 @@ packages: - pyyaml>=6.0.0 - shapely>=2.1.0 ; platform_machine != 'ARM64' or sys_platform != 'win32' - scipy>=1.10.0 - - cleopatra[tiles]>=0.28.0 ; extra == 'viz' + - cleopatra[tiles]>=0.30.0 ; extra == 'viz' - dask>=2024.1.0 ; extra == 'lazy' - distributed>=2024.1.0 ; extra == 'lazy' - zarr>=3 ; extra == 'lazy' @@ -36482,19 +36482,6 @@ packages: - pystac-client>=0.8.0 ; extra == 'stac' - stac-asset>=0.4.7 ; extra == 'stac' requires_python: '>=3.11,<4' -- pypi: git+https://github.com/serapeum-org/cleopatra.git?rev=cfa90c3322c4e6c0d8837d54af874172a02c1b50#cfa90c3322c4e6c0d8837d54af874172a02c1b50 - name: cleopatra - version: 0.29.0 - requires_dist: - - numpy>=2.0.0 - - matplotlib>=3.9 - - pillow>=12.1.1 - - imageio-ffmpeg>=0.4.9 - - hpc-utils>=0.1.4 - - pyproj>=3.7.2 ; extra == 'tiles' - - xyzservices>=2026.3.0 ; extra == 'tiles' - - cmap>=0.7.2 ; extra == 'science-colors' - requires_python: '>=3.11,<4' - pypi: https://files.pythonhosted.org/packages/01/bd/f8607e908605262e4926cbfd2560094bc5d04ef7f8aff1340e7fff503016/traitlets-5.16.0-py3-none-any.whl name: traitlets version: 5.16.0 @@ -40322,6 +40309,20 @@ packages: version: 0.13.0 sha256: 34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14 requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/78/a4/03e5f09af194fa1df1263f88e0ae7d0acb47a071ac638beaca57ed4bceff/cleopatra-0.30.0-py3-none-any.whl + name: cleopatra + version: 0.30.0 + sha256: 59624fd5e2a16cbace7829a7b22e039b2c458f6b6d3bba963badfd6a83411ec0 + requires_dist: + - hpc-utils>=0.1.4 + - imageio-ffmpeg>=0.4.9 + - matplotlib>=3.9 + - numpy>=2.0.0 + - pillow>=12.1.1 + - cmap>=0.7.2 ; extra == 'science-colors' + - pyproj>=3.7.2 ; extra == 'tiles' + - xyzservices>=2026.3.0 ; extra == 'tiles' + requires_python: '>=3.11,<4' - pypi: https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl name: scipy version: 1.18.0 diff --git a/pyproject.toml b/pyproject.toml index 0da53c7292..b12aa05d89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ dependencies = [ ] [project.optional-dependencies] -viz = ["cleopatra[tiles]>=0.28.0"] +viz = ["cleopatra[tiles]>=0.30.0"] lazy = [ "dask>=2024.1.0", "distributed>=2024.1.0", @@ -314,15 +314,6 @@ platforms = ["win-64", "linux-64", "linux-aarch64", "osx-arm64", "osx-64"] [tool.pixi.pypi-dependencies] pyramids-gis = { path = ".", editable = true } -# cleopatra's 0.30.0 restructure lands in that release; until then pyramids needs it -# from git. Pinned to an exact rev (not `branch = "main"`) so the build is reproducible -# and pixi actually re-fetches on a bump — this commit carries BOTH the subpackage -# layout AND the grouped render-parameter objects (ColorScaling / Contour / CellValues / -# DataStyle / PointOverlay / PanelLabels), which removed the loose styling keywords. -# It still self-reports 0.29.0. Dev/CI track this rev; the published [viz] extra stays a -# plain version spec so built wheels carry no direct-URL metadata (PyPI rejects it). -# Remove this and bump the [viz] floor to `>=0.30.0` once cleopatra 0.30.0 releases. -cleopatra = { git = "https://github.com/serapeum-org/cleopatra.git", rev = "cfa90c3322c4e6c0d8837d54af874172a02c1b50", extras = ["tiles"] } [tool.pixi.tasks] main = { cmd = "pytest -vvv --cov=src/pyramids -sv -m 'not plot and not xarray' --cov-report=xml", description = "Run the main test suite" } diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index e299530d79..99e289fd49 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -161,227 +161,51 @@ def _unwrap_geographic_longitude( return result -# Loose plot kwargs cleopatra 0.28 deprecated in favour of the typed specs -# (ColorBar / PointOverlay). ``render_array`` translates them to the spec at the -# render boundary (see ``_migrate_deprecated_plot_specs``) so cleopatra only ever -# sees the typed form — no double DeprecationWarning — and the loose forms are -# steered off in one place for every raster plot / animate path. The maps mirror -# cleopatra's own loose-kwarg -> spec-field mapping. -_CBAR_TO_COLORBAR = { - "cbar_label": "label", - "cbar_length": "length", - "cbar_label_size": "label_size", - "cbar_label_rotation": "label_rotation", - "cbar_label_location": "label_location", - "cbar_orientation": "orientation", - "ticks_spacing": "ticks_spacing", -} -_POINT_TO_OVERLAY = { - "point_color": "color", - "point_size": "size", - # Legacy ``pid_*`` aliases first, then the modern ``point_label_*`` names — the - # fold below is last-write-wins, so passing both makes ``point_label_*`` win, - # matching cleopatra's ``_resolve_point_overlay`` precedence. - "pid_color": "label_color", - "pid_size": "label_size", - "point_label_color": "label_color", - "point_label_size": "label_size", -} - - -def _migrate_deprecated_plot_specs( - kwargs: dict[str, Any], colorbar_cls: Any, point_overlay_cls: Any -) -> None: - """Fold deprecated loose ``cbar_*`` / ``point_*`` kwargs into the typed specs. - - Mutates ``kwargs`` in place: the loose keys are popped and folded into a - ``ColorBar`` / ``PointOverlay`` unless an explicit typed spec was already given. - A typed ``colorbar=ColorBar`` (or ``colorbar=False``, which hides the bar) and a - ``points=PointOverlay`` win — the loose keys are dropped with the warning. - ``colorbar=True`` / ``None`` still fold (they carry no styling of their own, so - dropping the loose kwargs would silently lose the caption). Emits a pyramids - ``DeprecationWarning`` naming the typed replacement. Cleopatra therefore never - sees the loose kwargs, so its own deprecation never fires — no double warning. - """ - cbar = { - _CBAR_TO_COLORBAR[k]: kwargs.pop(k) for k in _CBAR_TO_COLORBAR if k in kwargs +# Loose colour-bar kwargs that the typed ``pyramids.plot.ColorBar`` spec replaces. +# cleopatra 0.30 still accepts these natively, but pyramids standardises on ``ColorBar``, +# so passing one is a hard error pointing at the spec. ``cbar_kwargs`` (a raw matplotlib +# colorbar-kwargs passthrough) and ``add_colorbar`` (the on/off switch) are deliberately +# NOT in this set — they have no ``ColorBar`` field and remain valid. +_CBAR_REPLACED_BY_COLORBAR = frozenset( + { + "cbar_label", + "cbar_length", + "cbar_label_size", + "cbar_label_rotation", + "cbar_label_location", + "cbar_orientation", + "cbar_location", + "cbar_inside", + "cbar_box", + "cbar_label_color", + "cbar_tick_color", + "ticks_spacing", } - if cbar: - existing = kwargs.get("colorbar") - # Only a typed ColorBar (carries its own styling) or colorbar=False (bar - # hidden) can't be augmented by the loose kwargs. colorbar=True / None / - # absent carry no caption of their own, so fold the loose form in — else - # the caption that rendered before this migration would silently vanish. - if existing is False: - warnings.warn( - "The loose cbar_* / ticks_spacing kwargs are deprecated and were " - "ignored because colorbar=False hides the colour bar.", - DeprecationWarning, - stacklevel=3, - ) - elif isinstance(existing, colorbar_cls): - warnings.warn( - "The loose cbar_* / ticks_spacing kwargs are deprecated and were " - "ignored because an explicit colorbar=ColorBar was given; set the " - "styling on it instead.", - DeprecationWarning, - stacklevel=3, - ) - else: - warnings.warn( - "The loose cbar_* / ticks_spacing kwargs are deprecated; pass " - "colorbar=pyramids.plot.ColorBar(...) instead.", - DeprecationWarning, - stacklevel=3, - ) - kwargs["colorbar"] = colorbar_cls(**cbar) +) - style = { - _POINT_TO_OVERLAY[k]: kwargs.pop(k) for k in _POINT_TO_OVERLAY if k in kwargs - } - if style: - points = kwargs.get("points") - if isinstance(points, point_overlay_cls): - warnings.warn( - "The loose point_* / pid_* kwargs are deprecated and were ignored " - "because points= is already a PointOverlay; set them on it instead.", - DeprecationWarning, - stacklevel=3, - ) - elif points is None: - warnings.warn( - "The loose point_* / pid_* kwargs are deprecated and have no effect " - "without points=; pass points=pyramids.plot.PointOverlay(points, ...).", - DeprecationWarning, - stacklevel=3, - ) - else: - warnings.warn( - "The loose point_* / pid_* kwargs are deprecated; pass " - "points=pyramids.plot.PointOverlay(points, ...) instead.", - DeprecationWarning, - stacklevel=3, - ) - kwargs["points"] = point_overlay_cls(points, **style) - - -def _build_grouped_render_specs( - kwargs: dict[str, Any], *, include_cells: bool = True -) -> dict[str, Any]: - """Fold pyramids' loose scale / contour / cell / style kwargs into cleopatra groups. - - cleopatra 0.30 replaced the flat styling keywords on ``plot`` / ``animate`` / ``facet`` - with typed *group objects* and removed the shims that accepted the loose forms, so a - loose ``color_scale=`` / ``style=`` / ``levels=`` now raises. pyramids keeps its stable - loose-kwarg facade and translates them here, at the single render boundary, mirroring - :func:`_migrate_deprecated_plot_specs`. The loose keys are **popped** from ``kwargs`` in - place and the built group objects are returned keyed by their cleopatra render-method - parameter name (``color`` / ``contour`` / ``cells`` / ``data_style``). Only a group with - at least one set field is built, so an untouched call forwards nothing. - - Mapping (loose keyword -> group field): - - * ``color_scale`` / ``gamma`` / ``line_threshold`` / ``line_scale`` / ``bounds`` / - ``midpoint`` -> :class:`~cleopatra.styling.scaling.ColorScaling` (``midpoint`` maps to - its ``center`` field; ``color_scale`` selects the ``kind`` via ``ColorScale``). - * ``levels`` / ``label_kw`` -> :class:`~cleopatra.styling.params.Contour`. - * ``display_cell_value`` / ``num_size`` / ``background_color_threshold`` -> - :class:`~cleopatra.styling.params.CellValues` (``show`` / ``size`` / - ``background_threshold``). - * ``style`` / ``hillshade`` -> :class:`~cleopatra.styling.params.DataStyle`. A no-op - ``style=None`` or ``hillshade`` of ``None`` / ``False`` is dropped without building a - ``DataStyle`` (an empty-dict ``hillshade`` is "shade with defaults" and is kept). - Args: - kwargs: The render kwargs; mutated in place to pop the folded loose keys. - include_cells: When ``False`` (the mesh path — ``MeshGlyph.plot`` has no ``cells`` - parameter) the cell-value keys are left untouched. +def _reject_replaced_cbar_kwargs(kwargs: dict[str, Any]) -> None: + """Raise if any loose colour-bar kwarg the ``ColorBar`` spec replaces is present. - Returns: - A dict of the built cleopatra group objects, keyed by render-method parameter name. + Mirrors how cleopatra rejects the other loose styling keywords that moved onto typed + group objects. cleopatra itself still tolerates the ``cbar_*`` / ``ticks_spacing`` + forms, but pyramids exposes a single typed colour-bar surface — ``colorbar=ColorBar`` + — so it rejects the loose forms at the render boundary with a pointer to the spec. + + Args: + kwargs: The render kwargs. Raises: - ValueError: If ``color_scale`` is not a recognised - :class:`~cleopatra.styling.styles.ColorScale` value; the message lists the valid - options. + ValueError: If a replaced loose colour-bar keyword is present; the message lists + the offending keys and the ``ColorBar`` replacement. """ - from cleopatra.styling.params import CellValues, Contour, DataStyle - from cleopatra.styling.scaling import ColorScaling - from cleopatra.styling.styles import ColorScale - - specs: dict[str, Any] = {} - - scale_keys = ( - "color_scale", - "gamma", - "line_threshold", - "line_scale", - "bounds", - "midpoint", - ) - if any(k in kwargs for k in scale_keys): - color_scale = kwargs.pop("color_scale", None) - try: - kind = ( - ColorScale(color_scale) - if color_scale is not None - else ColorScale.LINEAR - ) - except ValueError: - valid = [s.value for s in ColorScale] - raise ValueError( - f"Unsupported color_scale {color_scale!r}; valid options: {valid}." - ) from None - scale_params: dict[str, Any] = {"kind": kind} - for src, dst in ( - ("gamma", "gamma"), - ("line_threshold", "line_threshold"), - ("line_scale", "line_scale"), - ("bounds", "bounds"), - ("midpoint", "center"), - ): - if src in kwargs: - scale_params[dst] = kwargs.pop(src) - specs["color"] = ColorScaling(**scale_params) - - if "levels" in kwargs or "label_kw" in kwargs: - contour_params: dict[str, Any] = {} - if "levels" in kwargs: - contour_params["levels"] = kwargs.pop("levels") - if "label_kw" in kwargs: - contour_params["label_kw"] = kwargs.pop("label_kw") - specs["contour"] = Contour(**contour_params) - - cell_keys = ("display_cell_value", "num_size", "background_color_threshold") - if include_cells and any(k in kwargs for k in cell_keys): - cell_params: dict[str, Any] = {} - if "display_cell_value" in kwargs: - cell_params["show"] = kwargs.pop("display_cell_value") - if "num_size" in kwargs: - cell_params["size"] = kwargs.pop("num_size") - if "background_color_threshold" in kwargs: - cell_params["background_threshold"] = kwargs.pop( - "background_color_threshold" - ) - specs["cells"] = CellValues(**cell_params) - - # style/hillshade -> DataStyle, honouring the historical no-op semantics: an explicit - # ``style=None`` requests no preset and a ``hillshade`` of ``None`` / ``False`` requests - # no shading, so neither builds a - # DataStyle. An empty-dict ``hillshade`` ({}) is falsy but means "shade with defaults", - # so identity checks (not truthiness) gate it. Always pop so a no-op never reaches - # cleopatra as a now-rejected loose keyword. - style = kwargs.pop("style", None) - hillshade = kwargs.pop("hillshade", None) - style_params: dict[str, Any] = {} - if style is not None: - style_params["style"] = style - if hillshade is not None and hillshade is not False: - style_params["hillshade"] = hillshade - if style_params: - specs["data_style"] = DataStyle(**style_params) - - return specs + offending = sorted(_CBAR_REPLACED_BY_COLORBAR & kwargs.keys()) + if offending: + raise ValueError( + f"The loose colour-bar kwargs {offending} were replaced by the typed " + "pyramids.plot.ColorBar spec; pass colorbar=ColorBar(label=…, orientation=…, " + "length=…, ...) instead of the loose cbar_* / ticks_spacing keywords." + ) def nonnull_group_kwargs(**groups: Any) -> dict[str, Any]: @@ -391,8 +215,7 @@ def nonnull_group_kwargs(**groups: Any) -> dict[str, Any]: (``color`` / ``contour`` / ``cells`` / ``data_style`` / ``classify``) as explicit typed params and fold the set ones into the kwargs forwarded to the render backend. Dropping the unset (``None``) groups keeps them off the render call, so an untouched - group never overrides the backend default and an explicitly-set one is routed straight - through (winning over the loose-kwarg translation — see :func:`render_array`). + group never overrides the backend default. Args: **groups: Candidate group objects keyed by their cleopatra render-method @@ -626,15 +449,16 @@ def render_array( PanelLabels, PointOverlay, ) - from cleopatra.styling.colorbar import ColorBar - - # Deprecate + translate the loose plot kwargs to the typed specs (cbar_* -> - # ColorBar, point_*/pid_* -> PointOverlay, dict basemap -> Basemap) so every - # raster plot/animate/facet call is steered onto the typed API in one place and - # cleopatra only ever sees the typed form. cleopatra >= 0.29 accepts - # ``colorbar=ColorBar`` on ``ArrayGlyph.facet`` too (serapeum-org/cleopatra#271), - # so faceting folds the loose kwargs like plot/animate — no exception. - _migrate_deprecated_plot_specs(kwargs, ColorBar, PointOverlay) + + # The loose styling kwargs are no longer translated here: they moved onto the typed + # render groups (color=ColorScaling / contour=Contour / cells=CellValues / + # data_style=DataStyle / points=PointOverlay / colorbar=ColorBar). Passing a form + # cleopatra removed (color_scale / style / levels / point_* / ...) surfaces cleopatra's + # own "moved onto a grouped parameter object" error; the loose cbar_* / ticks_spacing + # forms — which cleopatra still tolerates — are rejected here so pyramids exposes a + # single typed colour-bar surface (``colorbar=ColorBar``). + _reject_replaced_cbar_kwargs(kwargs) + # Translate the deprecated ``dict`` basemap alias to a ``Basemap``. if isinstance(basemap, dict) and basemap: warnings.warn( "Passing a dict as basemap= is deprecated; pass " @@ -687,21 +511,11 @@ def render_array( # The stretch parameters have been consumed by ``prepare_array`` above; # null them so the constructor call below cannot re-read inert values. rgb = surface_reflectance = cutoff = percentile = None - # cleopatra 0.30 replaced the flat styling keywords on plot/animate/facet with typed - # group objects and removed the loose-kwarg shims, so translate pyramids' loose - # color_scale / gamma / bounds / midpoint / levels / display_cell_value / style / - # hillshade (etc.) into the cleopatra ColorScaling / Contour / CellValues / DataStyle - # groups here, at the single render boundary. ``color_scale`` is validated inside the - # helper with a pyramids-side message listing the valid options, and a no-op style / - # hillshade is dropped. The built groups are render-method params, so they ride on the - # plot / animate / facet call (folded into ``render_kwargs`` below). - group_specs = _build_grouped_render_specs(kwargs) - # A bare ``(N, 3)`` points array is no longer auto-wrapped by cleopatra (it raises on # the overlay-drawing kinds, and is silently ignored on contour); wrap it in a - # ``PointOverlay`` so pyramids callers can keep passing a plain array. A points value - # already migrated to a ``PointOverlay`` by ``_migrate_deprecated_plot_specs`` (loose - # ``point_*`` present) is left as-is. + # ``PointOverlay`` so pyramids callers can keep passing a plain array (the ``points`` + # param is typed ``np.ndarray | PointOverlay``). A value already given as a + # ``PointOverlay`` is left as-is. points = kwargs.get("points") if points is not None and not isinstance(points, PointOverlay): kwargs["points"] = PointOverlay(np.asarray(points)) @@ -763,15 +577,9 @@ def render_array( ctor_kwargs[key] = value else: render_kwargs[key] = value - # The cleopatra group objects translated from pyramids' loose kwargs - # (``color`` / ``contour`` / ``cells`` / ``data_style``) are render-method params on - # ``plot`` / ``animate`` / ``facet``, not constructor options, so fold them into the - # render bucket (the animate merge below then picks them up too). ``setdefault`` — not - # ``update`` — so an explicitly-passed group object (already routed to render_kwargs by - # the split above, e.g. ``color=ColorScaling(...)``) wins over the one built from the - # loose kwargs; the loose translation only fills a group the caller did not set. - for _spec_key, _spec_val in group_specs.items(): - render_kwargs.setdefault(_spec_key, _spec_val) + # The cleopatra render groups (``color`` / ``contour`` / ``cells`` / ``data_style``) the + # facades pass are not constructor options, so the split above already routed them to + # ``render_kwargs`` — they ride on the plot / animate / facet call as-is. # The ``"animate"`` path only flows kwargs into ``cleo.animate(...)``, # not the constructor — keys like ``interval`` are valid for animate # but not in cleopatra's ``DEFAULT_OPTIONS`` and would trigger an @@ -1012,9 +820,8 @@ def mesh_render( if basemap and basemap_epsg is None: raise ValueError("Dataset must have a CRS (epsg) to use basemap.") require_cleopatra() - # ``plot_mesh_data`` translates the loose style/hillshade (and the other grouped - # styling kwargs) into cleopatra's ``DataStyle`` / ``ColorScaling`` / ``Contour`` - # groups at the MeshGlyph.plot boundary, so no pre-processing is needed here. + # ``plot_mesh_data`` forwards the typed render groups straight to ``MeshGlyph.plot`` and + # rejects the removed loose colour-bar kwargs, so no pre-processing is needed here. from pyramids.netcdf.ugrid.plot import plot_mesh_data result = plot_mesh_data(mesh, data, location=location, **kwargs) diff --git a/src/pyramids/dataset/abstract_dataset.py b/src/pyramids/dataset/abstract_dataset.py index 4a03e5d5c5..58d8a2bac3 100644 --- a/src/pyramids/dataset/abstract_dataset.py +++ b/src/pyramids/dataset/abstract_dataset.py @@ -1098,10 +1098,8 @@ def plot( the column index in the array. The second and third columns tell the location of the point in the array. To style the points, pass a ``pyramids.plot.PointOverlay(points, color=..., size=..., label_color=..., - label_size=...)`` instead — pyramids folds the loose ``point_color`` / ``point_size`` / - ``point_label_color`` / ``point_label_size`` / ``pid_color`` / ``pid_size`` kwargs into a - ``PointOverlay`` and emits a ``DeprecationWarning``; set the styling on the - ``PointOverlay`` instead. + label_size=...)`` instead of a bare array; the loose ``point_*`` / ``pid_*`` + styling kwargs were removed — set the styling on the ``PointOverlay``. figsize (tuple, optional): Figure size. The default is (8, 8). title (str, optional): diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index d74e74d30c..dbfbb321df 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2533,24 +2533,21 @@ def plot( points (np.ndarray or PointOverlay, optional): Point overlay. A 3-column array ``(value, row, col)`` draws unstyled points; to style them pass a ``pyramids.plot.PointOverlay(points, - color=…, size=…, label_color=…, label_size=…)`` instead (on cleopatra - >= 0.26 the loose ``point_color`` / ``point_size`` / ``pid_color`` / - ``pid_size`` kwargs are deprecated). Default ``None``. + color=…, size=…, label_color=…, label_size=…)`` instead (the loose + ``point_*`` / ``pid_*`` styling kwargs were removed). Default ``None``. color (ColorScaling, optional): - Typed colour-scale spec ``pyramids.plot.ColorScaling`` — the typed - equivalent of the loose ``color_scale`` / ``gamma`` / ``bounds`` / - ``midpoint`` kwargs; the explicit spec wins. Default ``None``. - contour (Contour, optional): - Typed contour-line spec ``pyramids.plot.Contour`` — the typed equivalent - of the loose ``levels`` / ``labels`` / ``label_kw`` kwargs. Default + Colour-scale spec ``pyramids.plot.ColorScaling`` (linear / power / sym-log / + boundary / midpoint norm), e.g. ``ColorScaling.power(gamma=0.7)``. Default ``None``. + contour (Contour, optional): + Contour-line spec ``pyramids.plot.Contour(levels=…, labels=…, label_kw=…)``. + Default ``None``. cells (CellValues, optional): - Typed per-cell annotation ``pyramids.plot.CellValues`` — the typed - equivalent of the loose ``display_cell_value`` / ``num_size`` / - ``background_color_threshold`` kwargs. Default ``None``. + Per-cell value annotation ``pyramids.plot.CellValues(show=…, size=…, + background_threshold=…)``. Default ``None``. data_style (DataStyle, optional): - Typed data-style / relief spec ``pyramids.plot.DataStyle`` — the typed - equivalent of the loose ``style`` / ``hillshade`` kwargs. Default ``None``. + Data-style / relief spec ``pyramids.plot.DataStyle(style=…, hillshade=…)``. + Default ``None``. animation_axis_values (sequence, optional): Per-frame labels for the animation, one per timestep. Defaults to the collection's ``time`` axis when set (e.g. dates parsed by @@ -2559,21 +2556,15 @@ def plot( exactly one label per timestep or a :class:`ValueError` is raised. Default ``None``. **kwargs: + Still-loose cleopatra render kwargs (colour-scale, contour, and cell-value + styling moved onto the ``color`` / ``contour`` / ``cells`` params above): + | Parameter | Type | Description | |----------------------------|-----------------------|-------------| | figsize | tuple, optional | Figure size. Default is `(8, 8)`. | | title | str, optional | Title of the plot. Default is `'Total Discharge'`. | | title_size | int, optional | Title size. Default is `15`. | - | color_scale | str, optional | Color-scale mode (default `"linear"`): one of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styling.styles.ColorScale` member. Integer codes are no longer accepted. | - | gamma | float, optional | Exponent for `color_scale="power"`. Default is `1/2`. | - | line_threshold | float, optional | `linthresh` for `color_scale="sym-lognorm"`. Default is `0.0001`. | - | line_scale | float, optional | `linscale` for `color_scale="sym-lognorm"`. Default is `0.001`. | - | bounds | list | Discrete bounds for `color_scale="boundary-norm"`. Default is `None`. | - | midpoint | float, optional | Midpoint value for `color_scale="midpoint"`. Default is `0`. | | cmap | str, optional | Color map style. Default is `'coolwarm_r'`. | - | display_cell_value | bool | Whether to display the values of the cells as text. | - | num_size | int, optional | Size of the numbers plotted on top of each cell. Default is `8`. | - | background_color_threshold | float \| int, optional| Threshold for deciding number color: if value > threshold -> black; else white. If `None`, uses `max_value/2`. Default is `None`. | Returns: diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index 36d27f8da5..b0d815e8cd 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -896,24 +896,18 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo title (str, optional): Axes title. Default is ``None`` (cleopatra's default title). color (ColorScaling, optional): - Typed colour-scale spec ``pyramids.plot.ColorScaling`` (linear / power / - sym-log / boundary / midpoint norm), e.g. - ``ColorScaling.power(gamma=0.7)``. The typed equivalent of the loose - ``color_scale`` / ``gamma`` / ``bounds`` / ``midpoint`` kwargs; when both - are given the explicit ``color`` wins. Default ``None``. + Colour-scale spec ``pyramids.plot.ColorScaling`` (linear / power / sym-log / + boundary / midpoint norm), e.g. ``ColorScaling.power(gamma=0.7)`` or + ``ColorScaling.boundary(bounds=[0, 0.5, 1])``. Default ``None``. contour (Contour, optional): - Typed contour-line spec ``pyramids.plot.Contour(levels=…, labels=…, - label_kw=…)``. The typed equivalent of the loose ``levels`` / ``labels`` / - ``label_kw`` kwargs; the explicit spec wins. Default ``None``. + Contour-line spec ``pyramids.plot.Contour(levels=…, labels=…, label_kw=…)``. + Default ``None``. cells (CellValues, optional): - Typed per-cell value annotation ``pyramids.plot.CellValues(show=…, size=…, - background_threshold=…)``. The typed equivalent of the loose - ``display_cell_value`` / ``num_size`` / ``background_color_threshold`` - kwargs; the explicit spec wins. Default ``None``. + Per-cell value annotation ``pyramids.plot.CellValues(show=…, size=…, + background_threshold=…)``. Default ``None``. data_style (DataStyle, optional): - Typed data-style / relief spec ``pyramids.plot.DataStyle(style=…, - hillshade=…)``. The typed equivalent of the loose ``style`` / ``hillshade`` - kwargs; the explicit spec wins. Default ``None``. + Data-style / relief spec ``pyramids.plot.DataStyle(style=…, hillshade=…)``. + Default ``None``. rgb_options (dict, optional): Grouped Sentinel-imagery kwargs. Accepted keys: ``"rgb"``, ``"surface_reflectance"``, ``"cutoff"``, diff --git a/src/pyramids/dataset/engines/analysis.py b/src/pyramids/dataset/engines/analysis.py index de69322668..925eec25e8 100644 --- a/src/pyramids/dataset/engines/analysis.py +++ b/src/pyramids/dataset/engines/analysis.py @@ -1957,10 +1957,10 @@ def plot( are dataset-type-specific decisions that belong on the facades. When the resolved band carries a GDAL colour table and the caller passes neither - ``cmap`` nor ``color_scale``, the raster renders through that palette: the colour - table is turned into a colormap and handed to cleopatra with - ``color_scale="boundary-norm"`` so each pixel value shows its own colour (#913). - An explicit ``cmap`` / ``color_scale`` opts out. + ``cmap`` nor ``color``, the raster renders through that palette: the colour table is + turned into a colormap and handed to cleopatra with + ``color=ColorScaling.boundary(bounds=...)`` so each pixel value shows its own colour + (#913). An explicit ``cmap`` / ``color`` opts out. The plot function uses the `cleopatra` as a backend to plot the raster data, for more information check [ArrayGlyph](https://serapeum-org.github.io/cleopatra/latest/api/array-glyph-class/#cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.plot). @@ -2008,34 +2008,26 @@ def plot( Requires the [viz] extra (mercantile, xyzservices, Pillow). A ``Basemap`` is not supported on the faceted path. kwargs: - The typed specs below (``ColorBar`` / ``PointOverlay`` / ``Basemap``) are - re-exported from ``pyramids.plot`` — import them from there rather than cleopatra. - Configure the colour bar through ``colorbar=ColorBar(label=..., length=..., - orientation=..., label_size=..., label_rotation=..., label_location=..., - ticks_spacing=...)``. The loose ``cbar_*`` / ``ticks_spacing`` kwargs it replaces - are deprecated: pyramids still accepts them but folds them into a ``ColorBar`` - and emits a ``DeprecationWarning``, so they are no longer listed below. The - same applies to ``point_*`` -> ``PointOverlay`` and a ``dict`` basemap -> - ``Basemap``. Migrate to the typed specs. + Colour-scale, contour, cell-value and data-style options moved onto typed + render groups (all re-exported from ``pyramids.plot``): pass + ``color=ColorScaling(...)`` / ``contour=Contour(...)`` / + ``cells=CellValues(...)`` / ``data_style=DataStyle(...)``, the colour bar as + ``colorbar=ColorBar(...)``, and point overlays as ``points=PointOverlay(...)``. + The loose forms they replace — ``color_scale`` / ``gamma`` / ``bounds`` / + ``midpoint`` / ``line_*`` / ``levels`` / ``display_cell_value`` / ``num_size`` / + ``background_color_threshold`` / ``style`` / ``hillshade`` / ``point_*`` / + ``cbar_*`` / ``ticks_spacing`` — are no longer accepted and now raise. The + remaining still-loose kwargs pass through to cleopatra: | Parameter | Type | Description | |-----------------------------|---------------------|-------------| - | `points` | array \\| PointOverlay | Point overlay. A 3-column array (value to display, row index, column index) draws unstyled points. To style them, pass a `pyramids.plot.PointOverlay(points, color=..., size=..., label_color=..., label_size=...)` instead — pyramids folds the loose `point_color` / `point_size` / `point_label_color` / `point_label_size` / `pid_color` / `pid_size` kwargs into a `PointOverlay` and emits a `DeprecationWarning`; set the styling on the `PointOverlay` instead. | + | `points` | array \\| PointOverlay | Point overlay. A bare 3-column array (value, row, col) draws unstyled points; pass `pyramids.plot.PointOverlay(points, color=..., size=..., label_color=..., label_size=...)` to style them. | + | `cmap` | str, optional | Color map style. Default is `'coolwarm_r'`. | | `figsize` | tuple, optional | Figure size. Default is `(8, 8)`. | | `title` | str, optional | Title of the plot. Default is `'Total Discharge'`. | | `title_size` | int, optional | Title size. Default is `15`. | - | `color_scale` | str, optional | Color-scale mode. One of `"linear"`, `"power"`, `"sym-lognorm"`, `"boundary-norm"`, `"midpoint"` (case-insensitive), or a `cleopatra.styling.styles.ColorScale` member. Integer codes are no longer accepted. Default is `"linear"`. | - | `gamma` | float, optional | Exponent for the `"power"` color scale. Default is `1/2`. | - | `line_threshold` | float, optional | `linthresh` for the `"sym-lognorm"` color scale. Default is `0.0001`. | - | `line_scale` | float, optional | `linscale` for the `"sym-lognorm"` color scale. Default is `0.001`. | - | `bounds` | list, optional | Discrete bounds for the `"boundary-norm"` color scale. Default is `None`. | - | `midpoint` | float, optional | Midpoint value for the `"midpoint"` color scale. Default is `0`. | - | `cmap` | str, optional | Color map style. Default is `'coolwarm_r'`. | - | `display_cell_value` | bool, optional | Whether to display cell values as text. | - | `num_size` | int, optional | Size of numbers plotted on top of each cell. Default is `8`. | - | `background_color_threshold`| float or int, optional | Threshold for deciding text color over cells: if value > threshold -> black text; else white text. If `None`, max value / 2 is used. Default is `None`. | | `add_colorbar` | bool, optional | Whether to draw the colour bar. Default is `True`. When `False`, no colorbar is created and the returned glyph's `cbar` is `None`. | - | `colorbar` | bool \\| ColorBar, optional | Colour-bar spec `pyramids.plot.ColorBar(label=…, length=…, orientation=…, label_size=…, label_rotation=…, label_location=…, ticks_spacing=…)` (cleopatra >= 0.28) — the complete, preferred replacement for the loose `cbar_*` / `ticks_spacing` kwargs. `False` hides it, `None` uses the default. | - | `full_bleed` | bool \\| str, optional | cleopatra >= 0.28 chrome-free layout: drop axes/margins so the array fills the figure. Default `False`. | + | `colorbar` | bool \\| ColorBar, optional | Colour-bar spec `pyramids.plot.ColorBar(label=…, length=…, orientation=…, label_size=…, label_rotation=…, label_location=…, ticks_spacing=…)` — the complete replacement for the removed loose `cbar_*` / `ticks_spacing` kwargs. `False` hides it, `None` uses the default. | + | `full_bleed` | bool \\| str, optional | Chrome-free layout: drop axes/margins so the array fills the figure. Default `False`. | Returns: ArrayGlyph: A cleopatra ``ArrayGlyph`` wrapping the rendered figure. The underlying matplotlib @@ -2066,27 +2058,28 @@ def plot( (
, ) ``` - - plot using power scale. + - plot using a power scale. ```python - >>> dataset.plot(band=0, color_scale="power") # doctest: +SKIP + >>> from pyramids.plot import ColorScaling # doctest: +SKIP + >>> dataset.plot(band=0, color=ColorScaling.power(gamma=0.7)) # doctest: +SKIP (
, ) ``` - - plot using SymLogNorm scale. + - plot using a SymLogNorm scale. ```python - >>> dataset.plot(band=0, color_scale="sym-lognorm") # doctest: +SKIP + >>> dataset.plot(band=0, color=ColorScaling.sym_log()) # doctest: +SKIP (
, ) ``` - - plot using PowerNorm scale. + - plot using a BoundaryNorm scale. ```python - >>> dataset.plot(band=0, color_scale="boundary-norm", bounds=[0, 0.2, 0.4, 0.6, 0.8, 1]) # doctest: +SKIP + >>> dataset.plot(band=0, color=ColorScaling.boundary(bounds=[0, 0.2, 0.4, 0.6, 0.8, 1])) # doctest: +SKIP (
, ) ``` - - plot using BoundaryNorm scale. + - plot using a midpoint scale. ```python - >>> dataset.plot(band=0, color_scale="midpoint") # doctest: +SKIP + >>> dataset.plot(band=0, color=ColorScaling.midpoint(at=0)) # doctest: +SKIP (
, ) ``` @@ -2126,26 +2119,27 @@ def plot( effective_extent = ( injected_extent if injected_extent is not None else self._ds.bbox ) - # Render a paletted band through its GDAL colour table (#913): build a - # discrete colormap from the palette and hand it to cleopatra as an - # explicit ``cmap`` + ``color_scale="boundary-norm"``. Only the single-band - # static path (no ``rgb``, no facet) carries a palette, and an explicit - # ``cmap`` / ``color_scale`` / ``bounds`` from the caller wins over it. + # Render a paletted band through its GDAL colour table (#913): build a discrete + # colormap from the palette and hand it to cleopatra as an explicit ``cmap`` plus a + # boundary-norm ``color=ColorScaling.boundary(bounds=...)`` (cleopatra 0.30 moved the + # colour scale onto the typed group). Only the single-band static path (no ``rgb``, + # no facet) carries a palette, and an explicit ``cmap`` / ``color`` from the caller + # wins over it. if ( mode == "plot" and rgb is None and kwargs.get("cmap") is None - and kwargs.get("color_scale") is None - and kwargs.get("bounds") is None + and kwargs.get("color") is None ): # Read only the resolved band's colour table, not every band's — the # full-dataset ``color_table`` rebuilds a row per entry for all bands. band_color_table = self._ds.bands._get_color_table(band=band) if not band_color_table.empty: + from cleopatra.styling.scaling import ColorScaling + cmap, bounds = self._palette_colormap(band_color_table) kwargs["cmap"] = cmap - kwargs["color_scale"] = "boundary-norm" - kwargs["bounds"] = bounds + kwargs["color"] = ColorScaling.boundary(bounds=bounds) return render_array( arr=arr, extent=effective_extent, @@ -2253,8 +2247,8 @@ def _palette_colormap(color_table: DataFrame) -> tuple[Any, list[float]]: :meth:`_process_color_table`) into a matplotlib ``ListedColormap`` carrying the palette's exact colours, and derives the ``BoundaryNorm`` bin edges from cleopatra's ``category_boundaries``. The colormap is handed to cleopatra as an - explicit ``cmap`` with ``color_scale="boundary-norm"`` so a paletted raster - renders through its own colours — pyramids only builds the mapping; cleopatra + explicit ``cmap`` with ``color=ColorScaling.boundary(bounds=...)`` so a paletted + raster renders through its own colours — pyramids only builds the mapping; cleopatra draws it. cleopatra renders with ``BoundaryNorm(bounds, ncolors=256)``, so the palette is diff --git a/src/pyramids/dataset/engines/vectorize.py b/src/pyramids/dataset/engines/vectorize.py index ed70d00ab4..09bcbd15bb 100644 --- a/src/pyramids/dataset/engines/vectorize.py +++ b/src/pyramids/dataset/engines/vectorize.py @@ -884,10 +884,11 @@ def cluster( >>> dataset = Dataset.create_from_array( ... arr, top_left_corner=top_left_corner, cell_size=cell_size, epsg=4326 ... ) - >>> dataset.plot( - ... color_scale="boundary-norm", bounds=[1, 1.9, 4.1, 5], display_cell_value=True, num_size=12, - ... background_color_threshold=5 - ... ) # doctest: +SKIP + >>> from pyramids.plot import ColorScaling, CellValues # doctest: +SKIP + >>> dataset.plot( # doctest: +SKIP + ... color=ColorScaling.boundary(bounds=[1, 1.9, 4.1, 5]), + ... cells=CellValues(show=True, size=12, background_threshold=5), + ... ) ``` ![cluster](./../../_images/dataset/cluster.png) diff --git a/src/pyramids/netcdf/_plot.py b/src/pyramids/netcdf/_plot.py index c4cd0d537d..3c2eac445f 100644 --- a/src/pyramids/netcdf/_plot.py +++ b/src/pyramids/netcdf/_plot.py @@ -501,13 +501,10 @@ def _build_render_kwargs( ("cmap", colour.cmap), ("vmin", colour.vmin), ("vmax", colour.vmax), - ("levels", colour.levels), ("norm", colour.norm), ("center", colour.center), ("extend", colour.extend), ("cbar_kwargs", colour.cbar_kwargs), - ("style", colour.style), - ("hillshade", colour.hillshade), ("ax", ax), ("figsize", figsize), ("title", title), @@ -516,6 +513,20 @@ def _build_render_kwargs( out[key] = value if colour.robust: out["robust"] = True + # cleopatra 0.30 moved ``levels`` / ``style`` / ``hillshade`` off the loose kwargs + # onto the typed render groups, so build them from the ColorOpts bag here (a no-op + # ``style=None`` / falsy ``hillshade`` is dropped, matching the render-group rules). + from cleopatra.styling.params import Contour, DataStyle + + if colour.levels is not None: + out["contour"] = Contour(levels=colour.levels) + data_style_fields: dict[str, Any] = {} + if colour.style is not None: + data_style_fields["style"] = colour.style + if colour.hillshade is not None and colour.hillshade is not False: + data_style_fields["hillshade"] = colour.hillshade + if data_style_fields: + out["data_style"] = DataStyle(**data_style_fields) # Curvilinear coord resolution. Priority (highest first): # 1. Explicit user `coords=`. diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index d7bc426d43..ec8a1cb51e 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -1783,20 +1783,18 @@ def plot( # type: ignore[override] when set — the multi-panel ``facet`` path has no ``points`` parameter and raises if one is supplied. Default is ``None``. color (ColorScaling, optional): - Typed colour-scale spec ``pyramids.plot.ColorScaling`` — the typed - equivalent of the loose ``color_scale`` / ``gamma`` / ``bounds`` / - ``midpoint`` kwargs; the explicit spec wins. Default ``None``. - contour (Contour, optional): - Typed contour-line spec ``pyramids.plot.Contour`` — the typed equivalent - of the loose ``levels`` / ``labels`` / ``label_kw`` kwargs. Default + Colour-scale spec ``pyramids.plot.ColorScaling`` (linear / power / sym-log / + boundary / midpoint norm), e.g. ``ColorScaling.power(gamma=0.7)``. Default ``None``. + contour (Contour, optional): + Contour-line spec ``pyramids.plot.Contour(levels=…, labels=…, label_kw=…)``. + Default ``None``. cells (CellValues, optional): - Typed per-cell annotation ``pyramids.plot.CellValues`` — the typed - equivalent of the loose ``display_cell_value`` / ``num_size`` / - ``background_color_threshold`` kwargs. Default ``None``. + Per-cell value annotation ``pyramids.plot.CellValues(show=…, size=…, + background_threshold=…)``. Default ``None``. data_style (DataStyle, optional): - Typed data-style / relief spec ``pyramids.plot.DataStyle`` — the typed - equivalent of the loose ``style`` / ``hillshade`` kwargs. Default ``None``. + Data-style / relief spec ``pyramids.plot.DataStyle(style=…, hillshade=…)``. + Default ``None``. **kwargs: Additional keyword arguments forwarded to :meth:`Analysis.plot `. diff --git a/src/pyramids/netcdf/ugrid/dataset.py b/src/pyramids/netcdf/ugrid/dataset.py index b9958871e0..12f1658b94 100644 --- a/src/pyramids/netcdf/ugrid/dataset.py +++ b/src/pyramids/netcdf/ugrid/dataset.py @@ -862,17 +862,14 @@ def plot( plot family, but a **no-op here** — ``kind`` selects a raster renderer (``imshow``/``pcolormesh``); the mesh always renders via ``tripcolor``/``tricontour``. Ignored. - color (ColorScaling, optional): Typed colour-scale spec - ``pyramids.plot.ColorScaling`` — the typed equivalent of the loose - ``color_scale`` / ``gamma`` / ``bounds`` / ``midpoint`` kwargs; the - explicit spec wins. Default ``None``. - contour (Contour, optional): Typed contour-line spec - ``pyramids.plot.Contour`` — the typed equivalent of the loose ``levels`` - / ``label_kw`` kwargs. Default ``None``. - data_style (DataStyle, optional): Typed data-style / relief spec - ``pyramids.plot.DataStyle`` — the typed equivalent of the loose ``style`` - / ``hillshade`` kwargs. (A mesh has no cell-value overlay, so there is no - ``cells`` param here.) Default ``None``. + color (ColorScaling, optional): Colour-scale spec + ``pyramids.plot.ColorScaling`` (linear / power / sym-log / boundary / + midpoint norm), e.g. ``ColorScaling.power(gamma=0.7)``. Default ``None``. + contour (Contour, optional): Contour-line spec + ``pyramids.plot.Contour(levels=…, label_kw=…)``. Default ``None``. + data_style (DataStyle, optional): Data-style / relief spec + ``pyramids.plot.DataStyle(style=…, hillshade=…)``. (A mesh has no + cell-value overlay, so there is no ``cells`` param here.) Default ``None``. **kwargs: Additional arguments passed to mesh_render (forwarded to plot_mesh_data). Notably ``colorbar`` (``bool``, default ``True``): pass ``colorbar=False`` to diff --git a/src/pyramids/netcdf/ugrid/plot.py b/src/pyramids/netcdf/ugrid/plot.py index ec279018a0..68246217b6 100644 --- a/src/pyramids/netcdf/ugrid/plot.py +++ b/src/pyramids/netcdf/ugrid/plot.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any from pyramids.base._utils import require_cleopatra -from pyramids.dataset._plot_helpers import _build_grouped_render_specs +from pyramids.dataset._plot_helpers import _reject_replaced_cbar_kwargs from pyramids.netcdf.ugrid.mesh import Mesh2d if TYPE_CHECKING: @@ -156,16 +156,12 @@ def plot_mesh_data( plot_kwargs["vmax"] = vmax plot_kwargs.update(kwargs) - # cleopatra 0.30 replaced MeshGlyph.plot's loose styling keywords with typed group - # objects; translate pyramids' loose color_scale / gamma / bounds / midpoint / levels / - # style / hillshade into cleopatra's ColorScaling / Contour / DataStyle groups. MeshGlyph - # has no cell-value overlay, so the cell-value keys are left untouched (include_cells). - # ``setdefault`` — so an explicitly-passed group object already in plot_kwargs (e.g. - # ``color=ColorScaling(...)`` from UgridDataset.plot) wins over the loose translation - # and the two never collide on the same keyword. - group_specs = _build_grouped_render_specs(plot_kwargs, include_cells=False) - for _spec_key, _spec_val in group_specs.items(): - plot_kwargs.setdefault(_spec_key, _spec_val) + # cleopatra 0.30's MeshGlyph.plot takes the typed render groups (color / contour / + # data_style) directly; the loose styling kwargs are no longer translated. A loose form + # cleopatra removed (color_scale / style / levels / ...) surfaces cleopatra's own + # "moved onto a grouped parameter object" error, and the loose cbar_* forms are rejected + # here so the mesh path shares pyramids' single typed colour-bar surface. + _reject_replaced_cbar_kwargs(plot_kwargs) glyph.plot( data, diff --git a/tests/dataset/plot/test_new_render_params.py b/tests/dataset/plot/test_new_render_params.py index b1de04a0ef..a6054c503d 100644 --- a/tests/dataset/plot/test_new_render_params.py +++ b/tests/dataset/plot/test_new_render_params.py @@ -5,8 +5,6 @@ proves the param reached the glyph rather than being dropped. """ -from unittest.mock import patch - import numpy as np import pytest @@ -87,84 +85,13 @@ def test_full_bleed_reaches_animate(self): ) assert isinstance(result, ArrayGlyph) - def test_deprecated_cbar_kwarg_folds_into_colorbar_and_renders(self): - """A loose `cbar_*` kwarg is folded into `colorbar=ColorBar(...)` and still renders. - - `render_array` translates the deprecated loose colour-bar kwargs into a typed - ColorBar before the cleopatra call (so cleopatra never sees the loose form); the - folded spec still renders the label. - """ - captured: dict = {} - original = ArrayGlyph.plot - - def spy(self, *args, **kwargs): - captured.update(kwargs) - return original(self, *args, **kwargs) - - with patch.object(ArrayGlyph, "plot", spy): - with pytest.warns(DeprecationWarning, match="cbar_"): - glyph = self._dataset().plot(band=0, cbar_label="mm", cbar_length=0.85) - assert "cbar_label" not in captured, ( - "loose cbar_label must be folded away, not passed loose" - ) - assert isinstance(captured.get("colorbar"), ColorBar), ( - "cbar_* must fold into a ColorBar" - ) - label = glyph.cbar.ax.get_ylabel() or glyph.cbar.ax.get_xlabel() - assert label == "mm", f"folded ColorBar label should render, got {label!r}" - - def test_explicit_colorbar_wins_over_loose_cbar(self): - """An explicit `colorbar=ColorBar` wins; the loose `cbar_*` are dropped (deprecated).""" - with pytest.warns( - DeprecationWarning, match="ignored because an explicit colorbar" - ): - glyph = self._dataset().plot( - band=0, colorbar=ColorBar(label="TYPED"), cbar_label="LOOSE" - ) - label = glyph.cbar.ax.get_ylabel() or glyph.cbar.ax.get_xlabel() - assert label == "TYPED", f"explicit ColorBar should win, got {label!r}" - - def test_colorbar_false_drops_loose_cbar_and_hides_bar(self): - """`colorbar=False` hides the bar and drops the loose `cbar_*` (not folded). - - The suppress half of the conflict contract: an explicit `colorbar=False` wins, - so the loose kwarg is dropped with a warning naming the `False` case and no - colour bar is drawn. - """ - with pytest.warns(DeprecationWarning, match="ignored because colorbar=False"): - glyph = self._dataset().plot(band=0, colorbar=False, cbar_label="x") - assert isinstance(glyph, ArrayGlyph) - assert getattr(glyph, "cbar", None) is None, "colorbar=False must hide the bar" - - def test_colorbar_true_still_folds_loose_cbar(self): - """`colorbar=True` + a loose `cbar_*` folds the styling in (not dropped). - - `True` / `None` carry no caption of their own, so the loose kwargs must still - render — only a typed `ColorBar` (or `colorbar=False`) suppresses them. - """ - with pytest.warns(DeprecationWarning, match="cbar_"): - glyph = self._dataset().plot(band=0, colorbar=True, cbar_label="DEPTH") - label = glyph.cbar.ax.get_ylabel() or glyph.cbar.ax.get_xlabel() - assert label == "DEPTH", ( - f"colorbar=True must keep the loose label, got {label!r}" - ) - - def test_deprecated_cbar_kwarg_folds_into_colorbar_on_the_facet_path(self): - """A loose `cbar_*` kwarg folds into a ColorBar and renders on the facet path. + def test_loose_cbar_kwarg_rejected(self): + """A loose `cbar_*` kwarg is rejected in favour of the typed `ColorBar` spec. - cleopatra >= 0.29 accepts `colorbar=ColorBar` on `ArrayGlyph.facet` - (serapeum-org/cleopatra#271), so the facet path folds the deprecated loose - `cbar_*` into a ColorBar (emitting a DeprecationWarning) like plot/animate; the - returned grid's shared colour bar still carries the label. + cleopatra 0.30 removed the loose colour-bar styling keywords; pyramids exposes a + single typed colour-bar surface, so passing a loose `cbar_*` through the + `Dataset.plot` facade raises a `ValueError` naming `pyramids.plot.ColorBar` + instead of being folded or forwarded. """ - stack = np.random.default_rng(3).random((3, 6, 6)).astype("float32") - with pytest.warns(DeprecationWarning, match="cbar_"): - result = render_array( - arr=stack, - mode="facet", - facet_kwargs={"col": "time", "col_coords": [0, 1, 2]}, - cbar_label="mm", - extent=[0.0, 0.0, 1.0, 1.0], - ) - label = result.cbar.ax.get_ylabel() or result.cbar.ax.get_xlabel() - assert label == "mm", f"facet colour-bar label should render, got {label!r}" + with pytest.raises(ValueError, match="ColorBar"): + self._dataset().plot(band=0, cbar_label="mm", cbar_length=0.85) diff --git a/tests/dataset/plot/test_plot_color.py b/tests/dataset/plot/test_plot_color.py index c04218456b..0239d85f20 100644 --- a/tests/dataset/plot/test_plot_color.py +++ b/tests/dataset/plot/test_plot_color.py @@ -278,14 +278,20 @@ def test_explicit_cmap_overrides_palette(self): @pytest.mark.plot def test_explicit_color_scale_overrides_palette(self): - """An explicit ``color_scale=`` opts out of the palette's boundary norm.""" - glyph = self._paletted_dataset().plot(band=0, color_scale="linear") + """An explicit ``color=ColorScaling`` opts out of the palette's boundary norm.""" + from pyramids.plot import ColorScaling + + glyph = self._paletted_dataset().plot(band=0, color=ColorScaling.linear()) assert type(glyph.im.norm).__name__ != "BoundaryNorm" @pytest.mark.plot def test_explicit_bounds_overrides_palette(self): - """A lone ``bounds=`` opts out of the palette rather than being overwritten.""" - glyph = self._paletted_dataset().plot(band=0, bounds=[0, 1, 2, 3]) + """An explicit ``color=ColorScaling.boundary`` opts out of the palette wiring.""" + from pyramids.plot import ColorScaling + + glyph = self._paletted_dataset().plot( + band=0, color=ColorScaling.boundary(bounds=[0, 1, 2, 3]) + ) assert glyph.im.cmap.name == "coolwarm_r" @pytest.mark.plot diff --git a/tests/dataset/plot/test_plot_dataset.py b/tests/dataset/plot/test_plot_dataset.py index bd9d98bb33..80a384fd43 100644 --- a/tests/dataset/plot/test_plot_dataset.py +++ b/tests/dataset/plot/test_plot_dataset.py @@ -153,15 +153,18 @@ def test_invalid_color_scale_raises(self, src: Dataset): dataset.plot(band=0, color_scale="bogus") @pytest.mark.plot - def test_valid_color_scale_any_case_passes(self, src: Dataset): - """A valid ``color_scale`` passes regardless of case. + def test_color_scaling_group_renders(self, src: Dataset): + """A ``color=ColorScaling`` spec validates and renders without raising. Test scenario: - ``ColorScale`` lookup is case-insensitive, so a mixed-case - ``"Power"`` must validate and render without raising. + cleopatra 0.30 moved the colour scale onto the typed ``ColorScaling`` + group, passed as ``color=``; a ``ColorScaling.power()`` spec must render + through ``Dataset.plot`` and return an ``ArrayGlyph``. """ + from pyramids.plot import ColorScaling + dataset = Dataset(src) - glyph = dataset.plot(band=0, color_scale="Power") + glyph = dataset.plot(band=0, color=ColorScaling.power()) assert isinstance(glyph, ArrayGlyph) @pytest.mark.plot diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index d69d5500e6..4c97e4bd45 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -151,9 +151,9 @@ def test_constructor_owns_cmap_vmin_vmax_cbar(self): ``cmap``, ``vmin``, ``vmax``, ``cbar_kwargs`` must reach ``ArrayGlyph.__init__`` so cleopatra's ``default_options`` is set in one place. None of them may also reach ``ArrayGlyph.plot``; otherwise the - value would be overwritten twice (the PR-6 D-4 fix). ``levels`` is no - longer a constructor option — cleopatra 0.30 moved it onto the - ``contour=Contour(...)`` render group (asserted separately below). + value would be overwritten twice (the PR-6 D-4 fix). ``cbar_kwargs`` (the + raw matplotlib passthrough) is not one of the loose ``cbar_*`` styling + keywords the ``ColorBar`` spec replaced, so it stays valid. """ fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(101) @@ -166,7 +166,6 @@ def test_constructor_owns_cmap_vmin_vmax_cbar(self): cmap="plasma", vmin=-1.0, vmax=2.0, - levels=10, cbar_kwargs={"orientation": "horizontal"}, ) for key in ("cmap", "vmin", "vmax", "cbar_kwargs"): @@ -174,11 +173,6 @@ def test_constructor_owns_cmap_vmin_vmax_cbar(self): assert key not in plot, ( f"`{key}` must NOT also reach cleo.plot; plot={plot}" ) - assert "levels" not in ctor, "`levels` folds into contour, not the ctor" - contour = plot.get("contour") - assert contour is not None and contour.levels == 10, ( - f"`levels` must fold into contour=Contour(levels=10); plot={plot}" - ) def test_render_call_only_kwargs_reach_plot(self): """Render-call-only kwargs reach ``ArrayGlyph.plot`` and nothing else. @@ -311,188 +305,45 @@ def test_split_is_driven_by_option_keys(self): f"`kind` must be force-routed to the render call; plot={plot}" ) - def test_deprecated_cbar_kwargs_fold_into_colorbar(self): - """Deprecated loose ``cbar_*`` kwargs are folded into a ColorBar, not passed loose. + def test_loose_cbar_kwarg_rejected(self): + """A loose ``cbar_*`` kwarg is rejected in favour of the typed ``ColorBar`` spec. Test scenario: - ``render_array`` translates the loose colour-bar kwargs into a - ``colorbar=ColorBar(...)`` before the cleopatra call, so neither the - constructor nor the render call ever sees a raw ``cbar_*``; the folded - ColorBar carries the mapped fields. Uses the ``_capture_calls`` fake. + cleopatra 0.30 removed the loose colour-bar styling keywords; pyramids + exposes a single typed colour-bar surface, so a loose ``cbar_label`` / + ``ticks_spacing`` raises a pyramids ``ValueError`` naming + ``pyramids.plot.ColorBar`` rather than being folded or forwarded. """ - fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(506) arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - with pytest.warns(DeprecationWarning, match="cbar_"): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - cbar_label="mm", - ticks_spacing=2, - ) - seen = {**ctor, **plot} - assert "cbar_label" not in seen, ( - f"loose cbar_label must be folded away; seen={sorted(seen)}" - ) - assert "ticks_spacing" not in seen, ( - f"loose ticks_spacing must be folded away; seen={sorted(seen)}" - ) - colorbar = plot.get("colorbar", ctor.get("colorbar")) - assert isinstance(colorbar, ColorBar), ( - f"cbar_* must fold into a ColorBar; got {colorbar!r}" - ) - assert colorbar.label == "mm" - assert colorbar.ticks_spacing == 2 + with pytest.raises(ValueError, match="ColorBar"): + render_array( + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + cbar_label="mm", + ticks_spacing=2, + ) - def test_deprecated_point_kwargs_fold_into_pointoverlay(self): - """Deprecated loose point_* kwargs are folded into a PointOverlay carrying the array. + def test_loose_point_kwarg_rejected(self): + """A loose ``point_*`` kwarg surfaces cleopatra's removed-keyword ``ValueError``. Test scenario: - ``render_array`` translates the loose point-style kwargs into a - ``points=PointOverlay(points, ...)`` before the cleopatra call, so the - raw ``point_*`` never reach cleopatra; the overlay carries the styling. + The point-styling keywords moved onto ``PointOverlay``; pyramids wraps a + bare ``points`` array in a ``PointOverlay`` but no longer translates the + loose ``point_*`` styling kwargs, so passing one reaches the real cleopatra + render call, which rejects it. """ - fake_cls, ctor, plot, _, _, _ = self._capture_calls() rng = np.random.default_rng(507) arr = rng.random((4, 4)).astype("float32") - pts = np.array([[1.0, 2, 3]]) - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - with pytest.warns(DeprecationWarning, match="point_"): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - points=pts, - point_color="red", - point_size=9, - ) - seen = {**ctor, **plot} - assert "point_color" not in seen, ( - f"loose point_color must be folded away; seen={sorted(seen)}" - ) - overlay = plot.get("points", ctor.get("points")) - assert isinstance(overlay, PointOverlay), ( - f"point_* must fold into a PointOverlay; got {overlay!r}" - ) - assert overlay.color == "red" - assert overlay.size == 9 - - def test_deprecated_pid_kwargs_fold_into_pointoverlay_labels(self): - """Legacy ``pid_*`` label kwargs fold into ``PointOverlay.label_*``. - - Test scenario: - The oldest loose aliases ``pid_color`` / ``pid_size`` map to the - overlay's ``label_color`` / ``label_size`` — the least-obvious mapping, - so pin it explicitly rather than trusting the map by inspection. - """ - fake_cls, ctor, plot, _, _, _ = self._capture_calls() - rng = np.random.default_rng(508) - arr = rng.random((4, 4)).astype("float32") - pts = np.array([[1.0, 2, 3]]) - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - with pytest.warns(DeprecationWarning, match="pid_"): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - points=pts, - pid_color="green", - pid_size=7, - ) - seen = {**ctor, **plot} - assert "pid_color" not in seen, ( - f"legacy pid_color must fold away; seen={sorted(seen)}" - ) - assert "pid_size" not in seen, ( - f"legacy pid_size must fold away; seen={sorted(seen)}" - ) - overlay = plot.get("points", ctor.get("points")) - assert isinstance(overlay, PointOverlay), ( - f"pid_* must fold into a PointOverlay; got {overlay!r}" - ) - assert overlay.label_color == "green" - assert overlay.label_size == 7 - - def test_point_label_alias_wins_over_legacy_pid(self): - """When both ``point_label_*`` and ``pid_*`` are passed, the modern name wins. - - Test scenario: - The fold is last-write-wins over ``_POINT_TO_OVERLAY`` insertion order - (``pid_*`` before ``point_label_*``), so passing both must land the - ``point_label_*`` value on the overlay — matching cleopatra's - ``_resolve_point_overlay`` precedence. Locks the fragile dict-order - invariant against a silent reorder. - """ - fake_cls, ctor, plot, _, _, _ = self._capture_calls() - rng = np.random.default_rng(509) - arr = rng.random((4, 4)).astype("float32") - pts = np.array([[1.0, 2, 3]]) - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - with pytest.warns(DeprecationWarning, match="point_"): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - points=pts, - pid_color="green", - point_label_color="purple", - ) - overlay = plot.get("points", ctor.get("points")) - assert overlay.label_color == "purple", ( - f"point_label_color must win over pid_color; got {overlay.label_color!r}" - ) - - def test_loose_point_kwargs_ignored_when_points_is_pointoverlay(self): - """A loose ``point_*`` is dropped (with a warning) when ``points`` is already typed. - - Test scenario: - An explicit ``PointOverlay`` wins — the loose ``point_color`` is popped - and warned as "ignored", and the overlay reaches the render call - untouched (its own styling preserved, not overwritten by the loose kwarg). - """ - fake_cls, ctor, plot, _, _, _ = self._capture_calls() - rng = np.random.default_rng(510) - arr = rng.random((4, 4)).astype("float32") - overlay_in = PointOverlay(np.array([[1.0, 2, 3]]), color="black") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - with pytest.warns(DeprecationWarning, match="ignored because points="): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - points=overlay_in, - point_color="red", - ) - seen = {**ctor, **plot} - assert "point_color" not in seen, "loose point_color must be dropped" - assert plot.get("points") is overlay_in, ( - "the explicit PointOverlay must reach the render call untouched" - ) - - def test_loose_point_kwargs_dropped_when_no_points(self): - """A loose ``point_*`` with no ``points`` is a no-op (warned and dropped). - - Test scenario: - Without a ``points`` array there is nothing to style, so the loose - ``point_color`` warns "no effect" and is dropped — never reaching - cleopatra, and no ``points`` kwarg is invented. - """ - fake_cls, ctor, plot, _, _, _ = self._capture_calls() - rng = np.random.default_rng(511) - arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - with pytest.warns(DeprecationWarning, match="no effect"): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - point_color="red", - ) - seen = {**ctor, **plot} - assert "point_color" not in seen, "loose point_color must be dropped" - assert "points" not in seen, "no points= may be invented from a loose kwarg" + with pytest.raises(ValueError): + render_array( + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + points=np.array([[1.0, 2, 3]]), + point_color="red", + ) @pytest.mark.plot def test_kind_contourf_reaches_plot_not_clobbered(self): @@ -551,27 +402,37 @@ class TestStyleHillshadePresets: not _supports_style, reason="cleopatra < 0.24 has no style presets" ) def test_style_preset_renders(self): - """A valid ``style`` preset name renders end to end. + """A ``data_style=DataStyle(style=...)`` preset renders end to end. Test scenario: - ``render_array(..., style="flow_accumulation")`` auto-routes the - preset to ``ArrayGlyph`` (``style`` is in ``option_keys()``) and - returns a rendered glyph — no pyramids-side handling needed. + cleopatra 0.30 moved the preset name onto the typed ``DataStyle`` group, + passed as ``data_style=``; ``render_array`` forwards it to the render call + and returns a rendered glyph. """ + from cleopatra.styling.params import DataStyle + rng = np.random.default_rng(737) arr = rng.random((6, 6)).astype("float32") glyph = render_array( - arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", style="flow_accumulation" + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + data_style=DataStyle(style="flow_accumulation"), ) assert isinstance(glyph, ArrayGlyph) @pytest.mark.skipif(not _supports_style, reason="cleopatra < 0.24 has no hillshade") def test_hillshade_renders(self): - """A ``hillshade`` blend renders end to end.""" + """A ``data_style=DataStyle(hillshade=True)`` blend renders end to end.""" + from cleopatra.styling.params import DataStyle + rng = np.random.default_rng(738) arr = rng.random((6, 6)).astype("float32") glyph = render_array( - arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade=True + arr=arr, + extent=[0.0, 0.0, 1.0, 1.0], + mode="plot", + data_style=DataStyle(hillshade=True), ) assert isinstance(glyph, ArrayGlyph) @@ -579,14 +440,16 @@ def test_hillshade_renders(self): not _supports_style, reason="cleopatra < 0.24 has no style presets" ) def test_dict_hillshade_renders(self): - """A dict ``hillshade`` (parameter passthrough) renders end to end.""" + """A dict ``hillshade`` on the ``DataStyle`` group renders end to end.""" + from cleopatra.styling.params import DataStyle + rng = np.random.default_rng(742) arr = rng.random((6, 6)).astype("float32") glyph = render_array( arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", - hillshade={"vert_exag": 8}, + data_style=DataStyle(hillshade={"vert_exag": 8}), ) assert isinstance(glyph, ArrayGlyph) @@ -594,14 +457,15 @@ def test_dict_hillshade_renders(self): not _supports_style, reason="cleopatra < 0.24 has no style presets" ) def test_style_and_hillshade_animate_renders(self): - """``style`` + ``hillshade`` render through the real animate path. + """``data_style`` renders through the real animate path. Test scenario: - Unlike the mocked NetCDF forwarding test, this drives cleopatra - 0.24's ``ArrayGlyph.animate(style=..., hillshade=...)`` for real - over a ``(time, rows, cols)`` stack, guarding the advertised - animated-shaded path against a future cleopatra signature change. + Drives cleopatra's ``ArrayGlyph.animate(data_style=...)`` for real over a + ``(time, rows, cols)`` stack, guarding the advertised animated-shaded path + against a future cleopatra signature change. """ + from cleopatra.styling.params import DataStyle + rng = np.random.default_rng(743) stack = rng.random((3, 6, 6)).astype("float32") glyph = render_array( @@ -609,69 +473,10 @@ def test_style_and_hillshade_animate_renders(self): extent=[0.0, 0.0, 1.0, 1.0], mode="animate", animation_axis_values=[0, 1, 2], - style="flow_accumulation", - hillshade=True, + data_style=DataStyle(style="flow_accumulation", hillshade=True), ) assert isinstance(glyph, ArrayGlyph) - @pytest.mark.parametrize("value", [False, None]) - def test_falsy_hillshade_is_dropped_not_forwarded(self, value): - """A ``None``/``False`` ``hillshade`` is dropped, never reaching cleopatra. - - Test scenario: - A falsy hillshade requests no shading, so ``render_array`` must pop - it before the ctor/render split — making it a true no-op on any - cleopatra version, not merely one that happens to accept the kwarg. - A fake glyph captures what actually reaches the constructor / plot - call, so this is independent of the installed cleopatra. - """ - fake_cls, ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() - rng = np.random.default_rng(745) - arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - render_array( - arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade=value - ) - assert "hillshade" not in ctor, f"hillshade={value!r} must not reach the ctor" - assert "hillshade" not in plot, f"hillshade={value!r} must not reach plot" - - def test_none_style_is_dropped_not_forwarded(self): - """An explicit ``style=None`` is dropped, never reaching cleopatra. - - Test scenario: - ``style=None`` requests no preset, so ``render_array`` must pop it - before the ctor/render split — a true no-op on any cleopatra version, - symmetric with the falsy-``hillshade`` drop. A fake glyph captures - what actually reaches the constructor / plot call. - """ - fake_cls, ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() - rng = np.random.default_rng(748) - arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - render_array(arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", style=None) - assert "style" not in ctor, "style=None must not reach the ctor" - assert "style" not in plot, "style=None must not reach plot" - - def test_empty_dict_hillshade_is_forwarded(self): - """An affirmative ``hillshade={}`` (default params) reaches the glyph. - - Test scenario: - ``{}`` is falsy but documented as "shade with default parameters", - so unlike ``False`` it must NOT be dropped — it is folded into a - cleopatra ``DataStyle(hillshade={})`` on the render call (cleopatra - 0.30 moved ``hillshade`` off the loose kwargs onto ``data_style=``). - """ - fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() - rng = np.random.default_rng(746) - arr = rng.random((5, 5)).astype("float32") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - render_array( - arr=arr, extent=[0.0, 0.0, 1.0, 1.0], mode="plot", hillshade={} - ) - data_style = plot.get("data_style") - assert data_style is not None, "affirmative hillshade={} must build a DataStyle" - assert data_style.hillshade == {}, "hillshade={} must reach the DataStyle" - @pytest.mark.skipif( not _supports_style, reason="cleopatra < 0.24 has no style presets" ) @@ -706,12 +511,16 @@ def test_returned_glyph_supports_apply_style(self): can re-apply a preset by name without rebuilding — verify the round trip through the ``Dataset.plot`` facade. """ + from cleopatra.styling.params import DataStyle + rng = np.random.default_rng(750) arr = rng.random((1, 8, 8)).astype("float32") dataset = Dataset.create_from_array( arr=arr, geo=(0, 0.1, 0, 2, 0, -0.1), epsg=4326 ) - glyph = dataset.plot(band=0, style="flow_accumulation") + glyph = dataset.plot( + band=0, data_style=DataStyle(style="flow_accumulation") + ) assert glyph.style == "flow_accumulation" glyph.apply_style("topography") assert glyph.style == "topography", ( @@ -989,31 +798,6 @@ def test_explicit_color_group_wins_over_loose_color_scale(self): ) assert plot.get("color") is explicit, "explicit color must win over color_scale" - def test_loose_kwargs_build_group_when_no_explicit(self): - """Loose ``color_scale`` / ``style`` still build the groups when unset explicitly. - - Test scenario: - With no explicit ``color`` / ``data_style``, the loose kwargs must translate - into ``ColorScaling`` / ``DataStyle`` on the render call (backward compat). - """ - from cleopatra.styling.params import DataStyle - from cleopatra.styling.scaling import ColorScaling - - fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() - rng = np.random.default_rng(12) - arr = rng.random((4, 4)).astype("float32") - with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): - render_array( - arr=arr, - extent=[0.0, 0.0, 1.0, 1.0], - mode="plot", - color_scale="power", - gamma=0.5, - style="flow_accumulation", - ) - assert isinstance(plot.get("color"), ColorScaling), "color_scale must build color" - assert isinstance(plot.get("data_style"), DataStyle), "style must build data_style" - def test_explicit_groups_reach_render_call(self): """Explicit ``contour`` / ``cells`` / ``data_style`` reach the render call. diff --git a/tests/dataset/plot/test_plot_facade.py b/tests/dataset/plot/test_plot_facade.py index 12697b3572..3c7ca6be08 100644 --- a/tests/dataset/plot/test_plot_facade.py +++ b/tests/dataset/plot/test_plot_facade.py @@ -79,50 +79,6 @@ def test_two_consecutive_calls_return_independent_figures(self): "Each call must own a distinct matplotlib Figure" ) - @pytest.mark.plot - @pytest.mark.parametrize( - "color_scale", - [ - "linear", - "power", - "sym-lognorm", - "boundary-norm", - "Power", - ], - ) - def test_color_scale_string_aliases_work(self, color_scale): - """``color_scale`` accepts the ``cleopatra.styling.styles.ColorScale`` aliases. - - Args: - color_scale: String alias for the colour-scale enum (lookup - is case-insensitive on the cleopatra side). - - Test scenario: - The plot docstrings document the string aliases as the valid - ``color_scale`` values. Verify each alias (including a - mixed-case spelling) is forwarded to cleopatra and yields an - ``ArrayGlyph``. - - Notes: - cleopatra's ``ColorScale`` StrEnum replaced the legacy - integer codes (``1``-``5``); those are no longer accepted — - see :meth:`test_color_scale_integer_codes_rejected`. - """ - rng = np.random.default_rng(1337) - arr = rng.random((6, 6)).astype("float32") - dataset = Dataset.create_from_array( - arr, top_left_corner=(0, 0), cell_size=0.05, epsg=4326 - ) - plot_kwargs = {"color_scale": color_scale} - if color_scale.lower() == "boundary-norm": - plot_kwargs["bounds"] = [0.0, 0.25, 0.5, 0.75, 1.0] - - result = dataset.plot(**plot_kwargs) - assert isinstance(result, ArrayGlyph), ( - f"color_scale={color_scale!r} should return ArrayGlyph, " - f"got {type(result).__name__}" - ) - @pytest.mark.plot @pytest.mark.parametrize("color_scale", [1, 2, 3, 4, 5, 0]) def test_color_scale_integer_codes_rejected(self, color_scale): diff --git a/tests/netcdf/plot/test_plot_options.py b/tests/netcdf/plot/test_plot_options.py index dc4ce8dfa3..f15c1222ce 100644 --- a/tests/netcdf/plot/test_plot_options.py +++ b/tests/netcdf/plot/test_plot_options.py @@ -213,14 +213,13 @@ def test_facet_none_takes_static_path(self): assert "_facet_stack" not in mock_plot.call_args.kwargs def test_style_and_hillshade_forwarded_to_engine(self): - """``ColorOpts(style=..., hillshade=...)`` forwards both flattened (#737). + """``ColorOpts(style=..., hillshade=...)`` forwards a ``DataStyle`` group (#737). Test scenario: - The cleopatra data-style preset name and the hillshade blend - are ``ColorOpts`` fields; the dataclass unpacking inside - ``NetCDF.plot`` must hand the engine flat ``style=`` / - ``hillshade=`` kwargs (the raster paths already auto-route - them to the glyph via ``render_array``). + The cleopatra data-style preset name and the hillshade blend are + ``ColorOpts`` fields; cleopatra 0.30 moved them onto the typed + ``DataStyle`` group, so the unpacking inside ``NetCDF.plot`` must hand the + engine a single ``data_style=DataStyle(style=..., hillshade=...)`` kwarg. """ nc = make_plot_3d_nc() var = nc.get_variable("t2m") @@ -231,8 +230,8 @@ def test_style_and_hillshade_forwarded_to_engine(self): colour=ColorOpts(style="flow_accumulation", hillshade={"vert_exag": 8}), ) forwarded = mock_plot.call_args.kwargs - assert forwarded["style"] == "flow_accumulation" - assert forwarded["hillshade"] == {"vert_exag": 8} + assert forwarded["data_style"].style == "flow_accumulation" + assert forwarded["data_style"].hillshade == {"vert_exag": 8} def test_style_and_hillshade_default_not_forwarded(self): """An unset ``style`` / ``hillshade`` leaks no kwarg to the engine. @@ -252,15 +251,15 @@ def test_style_and_hillshade_default_not_forwarded(self): assert "hillshade" not in forwarded def test_style_and_hillshade_survive_animate(self): - """``style`` / ``hillshade`` reach the animate render call (#737). + """The ``data_style`` group reaches the animate render call (#737). Test scenario: The animate path drops the static-only kwargs listed in ``_ANIMATE_DROP_KWARGS`` before calling ``render_array`` with - ``mode="animate"``. ``style`` / ``hillshade`` are *not* in - that drop-set, so a ``ColorOpts(style=..., hillshade=...)`` - must survive into the animated frames (cleopatra >= 0.24 - shades every frame). + ``mode="animate"``. The ``data_style`` group built from a + ``ColorOpts(style=..., hillshade=...)`` is *not* in that drop-set, so it + must survive into the animated frames (cleopatra >= 0.24 shades every + frame). """ nc = make_plot_3d_nc(n_times=3) captured: dict = {} @@ -274,5 +273,5 @@ def test_style_and_hillshade_survive_animate(self): colour=ColorOpts(style="topography", hillshade=True), ) assert captured["kw"]["mode"] == "animate" - assert captured["kw"]["style"] == "topography" - assert captured["kw"]["hillshade"] is True + assert captured["kw"]["data_style"].style == "topography" + assert captured["kw"]["data_style"].hillshade is True diff --git a/tests/netcdf/plot/test_plot_render.py b/tests/netcdf/plot/test_plot_render.py index 7bb5e7f8fc..e2987ec7ab 100644 --- a/tests/netcdf/plot/test_plot_render.py +++ b/tests/netcdf/plot/test_plot_render.py @@ -57,7 +57,12 @@ def test_robust_default_not_forwarded(self): assert "robust" not in mock_plot.call_args.kwargs def test_levels_extend_cbar_kwargs_forwarded(self): - """`levels=`, `extend=`, and `cbar_kwargs=` reach the renderer verbatim.""" + """`levels` folds into a `Contour` group; `extend`/`cbar_kwargs` stay loose. + + cleopatra 0.30 moved `levels` onto the typed `contour=Contour(levels=...)` + group, so `NetCDF.plot` builds that from `ColourOpts.levels`; `extend` and the + raw `cbar_kwargs` passthrough are still forwarded verbatim. + """ nc = make_plot_3d_nc() var = nc.get_variable("t2m") cbar = {"label": "test"} @@ -68,7 +73,7 @@ def test_levels_extend_cbar_kwargs_forwarded(self): colour=ColourOpts(levels=5, extend="both", cbar_kwargs=cbar), ) kw = mock_plot.call_args.kwargs - assert kw.get("levels") == 5 + assert kw.get("contour").levels == 5 assert kw.get("extend") == "both" assert kw.get("cbar_kwargs") == cbar diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index a69e872c3b..0309b5e647 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -276,42 +276,55 @@ def _dataset(location="face"): not _mesh_supports_style, reason="cleopatra < 0.24 has no MeshGlyph style" ) def test_dataset_plot_style_renders(self): - """``UgridDataset.plot(style=...)`` renders a styled MeshGlyph.""" - result = self._dataset("face").plot("depth", style="flow_accumulation") + """``UgridDataset.plot(data_style=DataStyle(style=...))`` renders a styled mesh.""" + from cleopatra.styling.params import DataStyle + + result = self._dataset("face").plot( + "depth", data_style=DataStyle(style="flow_accumulation") + ) assert isinstance(result, MeshGlyph) @pytest.mark.skipif( not _mesh_supports_style, reason="cleopatra < 0.24 has no MeshGlyph hillshade" ) def test_dataset_plot_hillshade_node_renders(self): - """``hillshade=`` renders on node-centered mesh data. + """``data_style=DataStyle(hillshade=True)`` renders on node-centered mesh data. cleopatra requires node-centered elevation for hillshade, so a node-location variable is used. """ - result = self._dataset("node").plot("depth", hillshade=True) + from cleopatra.styling.params import DataStyle + + result = self._dataset("node").plot( + "depth", data_style=DataStyle(hillshade=True) + ) assert isinstance(result, MeshGlyph) @pytest.mark.skipif( not _mesh_supports_style, reason="cleopatra < 0.24 has no MeshGlyph style" ) def test_style_and_hillshade_reach_mesh_glyph_plot(self): - """The presets are actually applied to ``MeshGlyph.plot`` (not just returned). + """The ``data_style`` group is actually applied to ``MeshGlyph.plot``. Test scenario: - ``isinstance(result, MeshGlyph)`` alone would still pass if the preset + ``isinstance(result, MeshGlyph)`` alone would still pass if the group were silently dropped between ``mesh_render`` and the glyph. Spy on - ``MeshGlyph.plot`` and assert ``style`` / ``hillshade`` arrive in its - call kwargs, proving the full facade -> mesh_render -> plot_mesh_data - -> MeshGlyph.plot leg forwards them. + ``MeshGlyph.plot`` and assert the ``data_style`` group arrives in its call + kwargs, proving the full facade -> mesh_render -> plot_mesh_data -> + MeshGlyph.plot leg forwards it. """ + from cleopatra.styling.params import DataStyle + ds = self._dataset("face") with patch.object(MeshGlyph, "plot") as mock_plot: - ds.plot("depth", style="flow_accumulation", hillshade=True) + ds.plot( + "depth", + data_style=DataStyle(style="flow_accumulation", hillshade=True), + ) kw = mock_plot.call_args.kwargs data_style = kw.get("data_style") assert data_style is not None, ( - "style/hillshade must fold into a DataStyle on MeshGlyph.plot" + "the data_style group must reach MeshGlyph.plot" ) assert data_style.style == "flow_accumulation" assert data_style.hillshade is True @@ -349,7 +362,11 @@ def test_returned_mesh_glyph_supports_apply_style(self): preset by name without rebuilding — verify the round trip through the ``UgridDataset.plot`` facade. """ - result = self._dataset("face").plot("depth", style="flow_accumulation") + from cleopatra.styling.params import DataStyle + + result = self._dataset("face").plot( + "depth", data_style=DataStyle(style="flow_accumulation") + ) assert result.style == "flow_accumulation" result.apply_style("bathymetry") assert result.style == "bathymetry", "apply_style must restyle in place" From aa0d0ea933d74800d4c955782ad6cdc15e272080 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 21:32:20 +0200 Subject: [PATCH 10/30] refactor(plot)!: drop the deprecated loose Sentinel kwargs from the plot facades MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lever 1 signature simplification: the four Sentinel-imagery kwargs (rgb / surface_reflectance / cutoff / percentile) were already deprecated on Dataset.plot and DatasetCollection.plot in favour of the grouped rgb_options= dict — remove the loose forms so the facades take rgb_options only. - Dataset.plot: 19 -> 15 params; DatasetCollection.plot: 17 -> 13. - replace Dataset._merge_rgb_options (loose+group merge with DeprecationWarnings) with a lean Dataset._unpack_rgb_options(rgb_options) — dict-only, same unknown-key ValueError. - the internal engines (Analysis.plot, render_array) keep rgb / surface_reflectance / cutoff / percentile; only the public facades lose the loose forms. - refreshed docstrings and deleted the obsolete loose-kwarg deprecation / collision tests. BREAKING CHANGE: Dataset.plot / DatasetCollection.plot no longer accept the top-level rgb / surface_reflectance / cutoff / percentile kwargs; pass them via rgb_options={"rgb": ..., "surface_reflectance": ..., "cutoff": ..., "percentile": ...}. Full plot surface: 1665 passed; mypy clean; doctests pass. --- src/pyramids/dataset/collection.py | 60 ++----- src/pyramids/dataset/dataset.py | 221 ++++-------------------- tests/dataset/plot/test_plot_dataset.py | 18 +- tests/dataset/plot/test_plot_facade.py | 177 ------------------- 4 files changed, 51 insertions(+), 425 deletions(-) diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index dbfbb321df..38561daa12 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2429,10 +2429,6 @@ def plot( self, band: int = 0, exclude_value: Any | None = None, - rgb: list[int] | None = None, - surface_reflectance: int | None = None, - cutoff: list | None = None, - percentile: int | None = None, rgb_options: dict | None = None, basemap: bool | str | dict[str, Any] | Basemap | None = None, frame_label: FrameLabel | None = None, @@ -2478,37 +2474,16 @@ def plot( Ignored when ``rgb`` is set (true-colour frames are not masked); passing it together with ``rgb`` emits a :class:`UserWarning`. - rgb (list[int], optional): - Band indices ``[red, green, blue]`` (or four, with - alpha) to composite into a true-colour time-lapse. When - set, every timestep is rendered as an RGB frame instead - of a single colormapped band, and the result is a - ``(time, rows, cols, 3)`` animation with no colorbar. - Each ``Dataset`` in the collection must carry at least - ``max(rgb) + 1`` bands. Default ``None`` (single-band - colormapped animation). **Deprecated**; pass via - ``rgb_options={"rgb": [...]}`` instead. - surface_reflectance (int, optional): - Surface-reflectance scale for normalising RGB bands - (e.g. ``10000`` for Sentinel-2, ``255`` for 8-bit). - Only used when ``rgb`` is set. Default ``None``. - **Deprecated**; pass via - ``rgb_options={"surface_reflectance": ...}``. - cutoff (list, optional): - Per-band clip values for the RGB stretch. Only used when - ``rgb`` is set. Default ``None``. **Deprecated**; pass - via ``rgb_options={"cutoff": ...}``. - percentile (int, optional): - Percentile stretch for the RGB bands (takes precedence - over ``surface_reflectance``). Only used when ``rgb`` is - set. Default ``None``. **Deprecated**; pass via - ``rgb_options={"percentile": ...}``. rgb_options (dict, optional): - Recommended grouped form of the RGB parameters above. - Accepted keys: ``"rgb"``, ``"surface_reflectance"``, - ``"cutoff"``, ``"percentile"``. Mirrors - :meth:`Dataset.plot`. On collision with a loose kwarg the - ``rgb_options`` value wins. Default ``None``. + Grouped Sentinel-imagery options for a true-colour time-lapse (mirrors + :meth:`Dataset.plot`). Accepted keys: ``"rgb"`` (band indices + ``[red, green, blue(, alpha)]`` — every timestep renders as an RGB frame, + a ``(time, rows, cols, 3)`` animation with no colorbar; each ``Dataset`` + must carry at least ``max(rgb) + 1`` bands), ``"surface_reflectance"`` + (scale for normalising RGB bands, e.g. ``10000`` for Sentinel-2), + ``"cutoff"`` (per-band clip values), ``"percentile"`` (percentile stretch, + takes precedence over ``surface_reflectance``). Default ``None`` + (single-band colormapped animation). basemap (bool, str, or Basemap, optional): Reference layer under the animation, dispatched by type. ``True`` or a tile-provider string (e.g. ``"CartoDB.Positron"``) overlays a @@ -2578,7 +2553,7 @@ def plot( ValueError: When ``rgb`` does not list exactly 3 (RGB) or 4 (RGBA) band indices, when any index is negative, or when the collection's datasets carry fewer than ``max(rgb) + 1`` bands. - Also raised (via ``_merge_rgb_options``) for an unknown key + Also raised (via ``_unpack_rgb_options``) for an unknown key in ``rgb_options``. Warns: @@ -2622,20 +2597,15 @@ def plot( See Also: - :meth:`pyramids.dataset.Dataset.plot`: The single-frame renderer (still or RGB still) for one ``Dataset``; shares the - ``rgb`` / ``rgb_options`` contract via ``_merge_rgb_options``. + ``rgb_options`` contract via ``_unpack_rgb_options``. - :func:`pyramids.dataset._plot_helpers.render_array`: The shared cleopatra dispatch that composites the true-colour frames for the animate path. """ - # Resolve the grouped ``rgb_options`` against the deprecated loose - # kwargs exactly as ``Dataset.plot`` does, so both facades share one - # RGB-parameter contract (and one deprecation message). - rgb, surface_reflectance, cutoff, percentile = Dataset._merge_rgb_options( - rgb_options=rgb_options, - rgb=rgb, - surface_reflectance=surface_reflectance, - cutoff=cutoff, - percentile=percentile, + # Unpack the grouped ``rgb_options`` exactly as ``Dataset.plot`` does, so both + # facades share one RGB-parameter contract. + rgb, surface_reflectance, cutoff, percentile = Dataset._unpack_rgb_options( + rgb_options ) # Frame labels for the animation. Default to the collection's time axis # when it has one (e.g. dates parsed from the file names by diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index b0d815e8cd..872d8e6743 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -807,12 +807,8 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo self, band: int | None = None, exclude_value: Any | None = None, - rgb: list[int] | None = None, - surface_reflectance: int | None = None, - cutoff: list | None = None, overview: bool | None = False, overview_index: int | None = 0, - percentile: int | None = None, basemap: bool | str | dict[str, Any] | Basemap | None = None, colorbar: bool | ColorBar | None = None, points: np.ndarray | PointOverlay | None = None, @@ -839,11 +835,8 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo facade defaults to band ``0``. See :meth:`Analysis.plot` for the full kwargs surface. - The four satellite-imagery kwargs ``rgb``, ``surface_reflectance``, ``cutoff``, - and ``percentile`` may be grouped under a single ``rgb_options=`` dict - (recommended) or passed loose at the top level (deprecated; emits - :class:`DeprecationWarning`). When both forms are mixed, the values inside - ``rgb_options`` win. + The four satellite-imagery options (``rgb``, ``surface_reflectance``, ``cutoff``, + ``percentile``) are passed through the single ``rgb_options=`` dict. Args: band (int, optional): @@ -851,28 +844,11 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo :meth:`_resolve_plot_band`. exclude_value (Any, optional): Pixel value to mask out before plotting. Default is ``None``. - rgb (list[int], optional): - **Deprecated**; pass via ``rgb_options={"rgb": [...]}`` instead. - Three- or four-element list of band indices ``[r, g, b]`` (optionally - ``[r, g, b, a]``) to render the dataset as a true-colour composite. - Only honoured when the dataset has at least 3 bands and at least one - band reports a colour interpretation. Default is ``None``. - surface_reflectance (int, optional): - **Deprecated**; pass via ``rgb_options={"surface_reflectance": ...}``. - Surface-reflectance scale factor used to normalise satellite reflectance - bands (typically ``10000`` for Sentinel-2). Default is ``None``. - cutoff (list, optional): - **Deprecated**; pass via ``rgb_options={"cutoff": ...}``. - Per-band clip values used when rendering RGB composites. Default is - ``None``. overview (bool, optional): If ``True``, plot the overview pyramid level instead of the full-resolution array. Default is ``False``. overview_index (int, optional): Index of the overview level to plot when ``overview=True``. Default is ``0``. - percentile (int, optional): - **Deprecated**; pass via ``rgb_options={"percentile": ...}``. - Percentile used when computing colour-scale limits. Default is ``None``. basemap (bool, str, or Basemap, optional): Reference layer, dispatched by type. ``True`` or a tile-provider string (e.g. ``"CartoDB.Positron"``) overlays a pyramids web-tile basemap. A @@ -909,11 +885,12 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo Data-style / relief spec ``pyramids.plot.DataStyle(style=…, hillshade=…)``. Default ``None``. rgb_options (dict, optional): - Grouped Sentinel-imagery kwargs. Accepted keys: - ``"rgb"``, ``"surface_reflectance"``, ``"cutoff"``, - ``"percentile"``. Recommended over the loose top-level - kwargs (which emit :class:`DeprecationWarning`). Default - is ``None``. + Grouped Sentinel-imagery options for a true-colour composite. Accepted + keys: ``"rgb"`` (3- or 4-element band-index list ``[r, g, b(, a)]``, only + honoured when the dataset has >= 3 bands and a colour interpretation), + ``"surface_reflectance"`` (reflectance scale factor, e.g. ``10000`` for + Sentinel-2), ``"cutoff"`` (per-band clip values), ``"percentile"`` + (percentile stretch). Default is ``None``. **kwargs: Additional keyword arguments forwarded verbatim to :meth:`Analysis.plot`. See that method for the full kwargs surface @@ -979,26 +956,10 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo ... rgb_options={"rgb": [0, 1, 2], "surface_reflectance": 255}, ... ) - ``` - - - The deprecated loose-kwarg form still works but emits a - :class:`DeprecationWarning`. New code should prefer the - grouped ``rgb_options=`` form shown above: - - ```python - >>> cleo = rgb_ds.plot( # doctest: +SKIP - ... rgb=[0, 1, 2], surface_reflectance=255, - ... ) - DeprecationWarning: Passing `rgb=`, `surface_reflectance=`... - ``` """ - rgb, surface_reflectance, cutoff, percentile = self._merge_rgb_options( - rgb_options=rgb_options, - rgb=rgb, - surface_reflectance=surface_reflectance, - cutoff=cutoff, - percentile=percentile, + rgb, surface_reflectance, cutoff, percentile = self._unpack_rgb_options( + rgb_options ) resolved_band, resolved_rgb = self._resolve_plot_band(band, rgb) # Spread the explicitly-set cleopatra render groups as their own ``**`` (not merged @@ -1027,123 +988,41 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo ) @staticmethod - def _merge_rgb_options( - *, + def _unpack_rgb_options( rgb_options: dict | None, - rgb: list[int] | None, - surface_reflectance: int | None, - cutoff: list | None, - percentile: int | None, ) -> tuple[list[int] | None, int | None, list | None, int | None]: - """Merge `rgb_options=` with the deprecated loose top-level kwargs. - - Returns the resolved four-tuple ``(rgb, surface_reflectance, - cutoff, percentile)`` passed forward to the renderer. Values in - ``rgb_options`` win over the loose kwargs on collision; using - any of the four loose kwargs emits a :class:`DeprecationWarning` - recommending the grouped form. + """Unpack the ``rgb_options`` group into the four Sentinel-imagery values. Args: - rgb_options: Recommended grouped form (``{"rgb": ..., ...}``). - Accepted keys: ``"rgb"``, ``"surface_reflectance"``, - ``"cutoff"``, ``"percentile"``. ``None`` means - no grouped options were supplied. - rgb: Deprecated top-level kwarg. - surface_reflectance: Deprecated top-level kwarg. - cutoff: Deprecated top-level kwarg. - percentile: Deprecated top-level kwarg. + rgb_options: Grouped Sentinel-imagery options, or ``None``. Accepted keys: + ``"rgb"``, ``"surface_reflectance"``, ``"cutoff"``, ``"percentile"``. Returns: - tuple: ``(rgb, surface_reflectance, cutoff, percentile)`` - resolved values, with the grouped form taking precedence. + tuple: ``(rgb, surface_reflectance, cutoff, percentile)`` — each the value from + ``rgb_options`` or ``None`` when absent. Raises: - ValueError: If ``rgb_options`` contains a key outside the - accepted set ``{"rgb", "surface_reflectance", "cutoff", - "percentile"}``. + ValueError: If ``rgb_options`` contains a key outside the accepted set. Examples: - - Pass everything through the grouped form (recommended). - No warnings are emitted and the returned four-tuple - mirrors the inputs in order: + - Unpack the grouped form (``None`` yields an all-``None`` tuple): ```python - >>> import warnings >>> from pyramids.dataset import Dataset - >>> with warnings.catch_warnings(record=True) as caught: - ... warnings.simplefilter("always") - ... result = Dataset._merge_rgb_options( - ... rgb_options={ - ... "rgb": [2, 1, 0], - ... "surface_reflectance": 10000, - ... }, - ... rgb=None, - ... surface_reflectance=None, - ... cutoff=None, - ... percentile=None, - ... ) - >>> result + >>> Dataset._unpack_rgb_options( + ... {"rgb": [2, 1, 0], "surface_reflectance": 10000} + ... ) ([2, 1, 0], 10000, None, None) - >>> [w.category.__name__ for w in caught] - [] - - ``` - - - Passing a value via the loose top-level kwarg path emits - a :class:`DeprecationWarning` that names the kwarg(s) - used and points to the grouped replacement: - - ```python - >>> import warnings - >>> from pyramids.dataset import Dataset - >>> with warnings.catch_warnings(record=True) as caught: - ... warnings.simplefilter("always") - ... result = Dataset._merge_rgb_options( - ... rgb_options=None, - ... rgb=[2, 1, 0], - ... surface_reflectance=None, - ... cutoff=None, - ... percentile=None, - ... ) - >>> result - ([2, 1, 0], None, None, None) - >>> caught[0].category.__name__ - 'DeprecationWarning' + >>> Dataset._unpack_rgb_options(None) + (None, None, None, None) ``` - - When both forms collide, ``rgb_options`` wins. A - :class:`DeprecationWarning` is still emitted for the loose - kwarg: + - An unknown key raises :class:`ValueError`: ```python - >>> import warnings - >>> from pyramids.dataset import Dataset - >>> with warnings.catch_warnings(record=True) as caught: - ... warnings.simplefilter("always") - ... result = Dataset._merge_rgb_options( - ... rgb_options={"rgb": [0, 1, 2]}, - ... rgb=[3, 4, 5], - ... surface_reflectance=None, - ... cutoff=None, - ... percentile=None, - ... ) - >>> result - ([0, 1, 2], None, None, None) - - ``` - - - An unknown key in ``rgb_options`` raises - :class:`ValueError`: - - ```python - >>> from pyramids.dataset import Dataset - >>> Dataset._merge_rgb_options( # doctest: +IGNORE_EXCEPTION_DETAIL - ... rgb_options={"unknown": 1}, - ... rgb=None, - ... surface_reflectance=None, - ... cutoff=None, - ... percentile=None, + >>> Dataset._unpack_rgb_options( # doctest: +IGNORE_EXCEPTION_DETAIL + ... {"unknown": 1} ... ) Traceback (most recent call last): ... @@ -1151,50 +1030,20 @@ def _merge_rgb_options( ``` """ - loose_pairs = { - "rgb": rgb, - "surface_reflectance": surface_reflectance, - "cutoff": cutoff, - "percentile": percentile, - } + accepted = ("rgb", "surface_reflectance", "cutoff", "percentile") opts = rgb_options or {} - unknown = set(opts) - set(loose_pairs) + unknown = set(opts) - set(accepted) if unknown: raise ValueError( f"Unknown keys in `rgb_options`: {sorted(unknown)}. " - f"Accepted: {sorted(loose_pairs)}." - ) - used_loose = [name for name, value in loose_pairs.items() if value is not None] - # Split the deprecated loose kwargs into those overridden by a - # matching `rgb_options` key (a collision — the loose value is - # discarded) and those used on their own. They get distinct - # warning text so the message isn't misleading: a user who *did* - # use the grouped form shouldn't be told "group them" again. - overridden = [name for name in used_loose if name in opts] - pure_loose = [name for name in used_loose if name not in opts] - if pure_loose: - warnings.warn( - "Passing " - + ", ".join(f"`{name}=`" for name in pure_loose) - + " as top-level kwargs to `Dataset.plot` is deprecated. " - "Group them under `rgb_options={...}` instead.", - DeprecationWarning, - stacklevel=3, + f"Accepted: {sorted(accepted)}." ) - if overridden: - warnings.warn( - "Both the loose " - + ", ".join(f"`{name}=`" for name in overridden) - + " kwarg(s) and `rgb_options` were given for the same key(s); " - "`rgb_options` wins — drop the loose form.", - DeprecationWarning, - stacklevel=3, - ) - rgb = opts.get("rgb", rgb) - surface_reflectance = opts.get("surface_reflectance", surface_reflectance) - cutoff = opts.get("cutoff", cutoff) - percentile = opts.get("percentile", percentile) - return rgb, surface_reflectance, cutoff, percentile + return ( + opts.get("rgb"), + opts.get("surface_reflectance"), + opts.get("cutoff"), + opts.get("percentile"), + ) def crop(self, *args, **kwargs): """Facade — delegates to :meth:`Spatial.crop `.""" diff --git a/tests/dataset/plot/test_plot_dataset.py b/tests/dataset/plot/test_plot_dataset.py index 80a384fd43..553020426b 100644 --- a/tests/dataset/plot/test_plot_dataset.py +++ b/tests/dataset/plot/test_plot_dataset.py @@ -433,7 +433,7 @@ def test_multi_band( src_arr: np.ndarray, ): dataset = Dataset(sentinel_raster) - array_glyph = dataset.plot(rgb=[3, 2, 1]) + array_glyph = dataset.plot(rgb_options={"rgb": [3, 2, 1]}) assert isinstance(array_glyph, ArrayGlyph) @pytest.mark.plot @@ -560,14 +560,6 @@ def test_rgb_insufficient_bands_raises(self, tmp_path): with pytest.raises(ValueError, match="needs at least 3 bands"): cube.plot(rgb_options={"rgb": [0, 1, 2]}) - @pytest.mark.plot - def test_rgb_loose_kwarg_is_deprecated(self, tmp_path): - """The loose top-level `rgb=` still works but warns, mirroring Dataset.plot.""" - cube = self._rgb_cube(tmp_path, n_times=3) - with pytest.warns(DeprecationWarning, match="rgb_options"): - glyph = cube.plot(rgb=[0, 1, 2], percentile=2) - assert glyph.arr.shape == (3, 8, 8, 3) - @pytest.mark.plot def test_single_band_path_unchanged(self, tmp_path): """Without `rgb`, plot() still yields a colormapped single-band animation.""" @@ -643,14 +635,6 @@ def test_rgb_with_exclude_value_warns(self, tmp_path): glyph = cube.plot(exclude_value=0, rgb_options={"rgb": [0, 1, 2]}) assert glyph.arr.shape == (2, 8, 8, 3), "RGB stack still rendered" - @pytest.mark.plot - def test_rgb_options_wins_over_loose_kwarg(self, tmp_path): - """On collision, `rgb_options` wins over the loose kwarg and still warns.""" - cube = self._rgb_cube(tmp_path, n_times=2, n_bands=4) - with pytest.warns(DeprecationWarning, match="rgb_options` wins"): - glyph = cube.plot(rgb=[1, 2, 3], rgb_options={"rgb": [0, 1, 2]}) - assert glyph.arr.shape == (2, 8, 8, 3), "grouped rgb composited every frame" - def _make_nc_subset_with_band_count(tmp_path, n_bands: int): """Build a NetCDF variable subset whose ``band_count`` is ``n_bands``. diff --git a/tests/dataset/plot/test_plot_facade.py b/tests/dataset/plot/test_plot_facade.py index 3c7ca6be08..65a831d1a8 100644 --- a/tests/dataset/plot/test_plot_facade.py +++ b/tests/dataset/plot/test_plot_facade.py @@ -177,48 +177,6 @@ def test_rgb_options_carries_all_four_keys(self): assert call_kwargs["cutoff"] == opts["cutoff"] assert call_kwargs["percentile"] == opts["percentile"] - @pytest.mark.plot - def test_loose_rgb_emits_deprecation_warning(self): - """Passing `rgb=` loose at the top level emits DeprecationWarning.""" - rng = np.random.default_rng(13) - arr = rng.random((3, 6, 6)).astype("float32") - dataset = Dataset.create_from_array( - arr, top_left_corner=(0, 0), cell_size=0.05, epsg=4326 - ) - with patch.object(type(dataset.analysis), "plot", autospec=True) as mock_plot: - mock_plot.return_value = "stub" - with pytest.warns(DeprecationWarning, match=r"rgb_options"): - dataset.plot(rgb=[0, 1, 2]) - assert mock_plot.call_args.kwargs["rgb"] == [0, 1, 2] - - @pytest.mark.plot - @pytest.mark.parametrize( - "key,value", - [ - ("surface_reflectance", 10000), - ("cutoff", [0.1, 0.9]), - ("percentile", 2), - ], - ) - def test_other_loose_kwargs_emit_deprecation_warning(self, key, value): - """Each Sentinel-loose kwarg emits a DeprecationWarning individually. - - Args: - key: One of ``"surface_reflectance"`` / ``"cutoff"`` / - ``"percentile"``. - value: Test value to pass in for that kwarg. - """ - rng = np.random.default_rng(14) - arr = rng.random((3, 6, 6)).astype("float32") - dataset = Dataset.create_from_array( - arr, top_left_corner=(0, 0), cell_size=0.05, epsg=4326 - ) - with patch.object(type(dataset.analysis), "plot", autospec=True) as mock_plot: - mock_plot.return_value = "stub" - with pytest.warns(DeprecationWarning, match=r"rgb_options"): - dataset.plot(**{key: value}) - assert mock_plot.call_args.kwargs[key] == value - @pytest.mark.plot def test_rgb_options_unknown_key_raises(self): """Unknown keys in `rgb_options` are rejected with a clear error.""" @@ -230,60 +188,6 @@ def test_rgb_options_unknown_key_raises(self): with pytest.raises(ValueError, match=r"Unknown keys"): dataset.plot(rgb_options={"bogus": True}) - @pytest.mark.plot - def test_rgb_options_overrides_loose_kwarg(self): - """When both forms are passed, `rgb_options` wins for the same key.""" - rng = np.random.default_rng(16) - arr = rng.random((3, 6, 6)).astype("float32") - dataset = Dataset.create_from_array( - arr, top_left_corner=(0, 0), cell_size=0.05, epsg=4326 - ) - with patch.object(type(dataset.analysis), "plot", autospec=True) as mock_plot: - mock_plot.return_value = "stub" - with pytest.warns(DeprecationWarning): - dataset.plot( - rgb=[2, 1, 0], - rgb_options={"rgb": [0, 1, 2]}, - ) - assert mock_plot.call_args.kwargs["rgb"] == [0, 1, 2] - - def test_collision_warning_distinguishes_from_pure_loose(self): - """A loose+grouped collision warns "rgb_options wins", not "group them" (M2). - - Test scenario: - M2 fix — when both `rgb=` (loose) and `rgb_options={"rgb": ...}` - are passed for the same key, the DeprecationWarning must say - the loose value was overridden ("`rgb_options` wins — drop - the loose form"), *not* the misleading "Group them under - `rgb_options={...}` instead" message that implies the user - forgot the grouped form. Conversely, a pure-loose call (no - `rgb_options`) still gets the original "group them" wording. - """ - rng = np.random.default_rng(17) - arr = rng.random((3, 6, 6)).astype("float32") - dataset = Dataset.create_from_array( - arr, top_left_corner=(0, 0), cell_size=0.05, epsg=4326 - ) - with patch.object(type(dataset.analysis), "plot", autospec=True) as mock_plot: - mock_plot.return_value = "stub" - with warnings.catch_warnings(record=True) as collide: - warnings.simplefilter("always") - dataset.plot(rgb=[2, 1, 0], rgb_options={"rgb": [0, 1, 2]}) - with warnings.catch_warnings(record=True) as pure: - warnings.simplefilter("always") - dataset.plot(rgb=[2, 1, 0]) - collide_msg = " ".join(str(w.message) for w in collide) - pure_msg = " ".join(str(w.message) for w in pure) - assert ( - "rgb_options` wins" in collide_msg and "drop the loose form" in collide_msg - ), f"collision warning should say rgb_options wins; got: {collide_msg!r}" - assert "Group them under" not in collide_msg, ( - f"collision warning must not use the 'group them' wording; got: {collide_msg!r}" - ) - assert "Group them under" in pure_msg, ( - f"pure-loose warning should keep the 'group them' wording; got: {pure_msg!r}" - ) - class TestDatasetPlotRgbOptionsEdges: """PR-4 / D-3 edge cases for ``rgb_options=`` not in :class:`TestDatasetPlotRgbOptions`. @@ -374,84 +278,3 @@ def test_empty_rgb_options_dict_is_noop(self, multiband_dataset): ] assert not deprecations, "Empty rgb_options must not emit DeprecationWarning" - @pytest.mark.plot - def test_loose_rgb_with_empty_group_still_warns(self, multiband_dataset): - """Loose `rgb=` with `rgb_options={}` still triggers the deprecation. - - Test scenario: - The warning is gated on the loose kwargs being not-None - (not on whether ``rgb_options`` is provided). An empty - group doesn't suppress the warning when a loose kwarg is - present. The loose ``rgb`` survives because the empty - group has no entries to overwrite it. - """ - with patch.object( - type(multiband_dataset.analysis), "plot", autospec=True - ) as mock_plot: - mock_plot.return_value = "stub" - with pytest.warns(DeprecationWarning, match=r"rgb_options"): - multiband_dataset.plot(rgb=[0, 1, 2], rgb_options={}) - assert mock_plot.call_args.kwargs["rgb"] == [ - 0, - 1, - 2, - ], "Loose rgb must survive when rgb_options is empty" - - @pytest.mark.plot - def test_only_loose_surface_reflectance_emits_warning_once(self, multiband_dataset): - """A single loose `surface_reflectance=` triggers exactly one warning. - - Test scenario: - The merge helper emits one warning regardless of how many - loose kwargs were passed; with only one set, exactly one - DeprecationWarning fires. Verifies the warning is not - spammed per-kwarg. - """ - with patch.object( - type(multiband_dataset.analysis), "plot", autospec=True - ) as mock_plot: - mock_plot.return_value = "stub" - with warnings.catch_warnings(record=True) as captured: - warnings.simplefilter("always") - multiband_dataset.plot(surface_reflectance=10000) - deprecations = [ - w for w in captured if issubclass(w.category, DeprecationWarning) - ] - assert len(deprecations) == 1, ( - f"Exactly one DeprecationWarning expected, got {len(deprecations)}: " - f"{[str(w.message) for w in deprecations]}" - ) - assert "surface_reflectance" in str(deprecations[0].message), ( - f"Warning must name the loose kwarg, got: {deprecations[0].message}" - ) - - @pytest.mark.plot - def test_rgb_options_only_partial_override(self, multiband_dataset): - """`rgb_options` overrides only its own keys; other loose kwargs survive. - - Test scenario: - Pass loose ``percentile=2`` AND - ``rgb_options={"rgb": [0, 1, 2]}``. The grouped form - overrides only ``rgb`` (which wasn't set loose), and the - loose ``percentile`` propagates unchanged. The - DeprecationWarning still fires because at least one loose - kwarg was used. - """ - with patch.object( - type(multiband_dataset.analysis), "plot", autospec=True - ) as mock_plot: - mock_plot.return_value = "stub" - with pytest.warns(DeprecationWarning, match=r"percentile"): - multiband_dataset.plot( - percentile=2, - rgb_options={"rgb": [0, 1, 2]}, - ) - call_kwargs = mock_plot.call_args.kwargs - assert call_kwargs["rgb"] == [ - 0, - 1, - 2, - ], f"Grouped rgb must propagate, got: {call_kwargs.get('rgb')}" - assert call_kwargs["percentile"] == 2, ( - f"Loose percentile must survive, got: {call_kwargs.get('percentile')}" - ) From 9ff9834da021d3d32e46692100536dd09c3c25c8 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 21:41:53 +0200 Subject: [PATCH 11/30] refactor(plot)!: tidy the RasterBase.plot ABC to match the facades Align the abstract plot contract with the concrete facades: drop the loose rgb / surface_reflectance / cutoff params (removed in the previous commit) and declare the grouped rgb_options= dict instead. RasterBase.plot is an @abstractmethod (body pass), so this is a signature + docstring change only. Updated the ABC signature-contract tests: replace the rgb / surface_reflectance default-checks with an rgb_options default-check and a guard that the loose Sentinel params are gone. Full plot surface: 1665 passed; mypy clean. --- src/pyramids/dataset/abstract_dataset.py | 17 +++------- tests/dataset/plot/test_plot_resolution.py | 36 ++++++++++------------ 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/pyramids/dataset/abstract_dataset.py b/src/pyramids/dataset/abstract_dataset.py index 58d8a2bac3..4c42570326 100644 --- a/src/pyramids/dataset/abstract_dataset.py +++ b/src/pyramids/dataset/abstract_dataset.py @@ -1062,11 +1062,9 @@ def plot( self, band: int | None = None, exclude_value: Any | None = None, - rgb: list[int] | None = None, - surface_reflectance: int | None = None, - cutoff: list | None = None, overview: bool = False, overview_index: int = 0, + rgb_options: dict | None = None, **kwargs, ): """Plot. @@ -1078,19 +1076,14 @@ def plot( The band you want to get its data. Default is 0. exclude_value (Any, optional): Value to exclude from the plot. Default is None. - rgb (List[int], optional): - RGB band indices. Default is [3, 2, 1]. - surface_reflectance (int | None, optional): - Surface reflectance value used to normalise satellite reflectance bands - (typically ``10000`` for Sentinel-2). Default is ``None`` — concrete - subclasses are responsible for picking a meaningful default when relevant. - cutoff (List, optional): - Clip the range of pixel values for each band (take only the pixel values from 0 to the value of - the cutoff and scale them back to between 0 and 1). Default is None. overview (bool, optional): True if you want to plot the overview. Default is False. overview_index (int, optional): Index of the overview. Default is 0. + rgb_options (dict, optional): + Grouped Sentinel-imagery options for a true-colour composite. Accepted + keys: ``"rgb"`` (band indices), ``"surface_reflectance"``, ``"cutoff"``, + ``"percentile"``. Default is ``None``. **kwargs: Additional plotting options. points (array | PointOverlay): Point overlay. A 3 column array with the first column as the value you want to display for diff --git a/tests/dataset/plot/test_plot_resolution.py b/tests/dataset/plot/test_plot_resolution.py index 0facee8d77..7807647167 100644 --- a/tests/dataset/plot/test_plot_resolution.py +++ b/tests/dataset/plot/test_plot_resolution.py @@ -461,19 +461,6 @@ def test_facade_routes_through_resolver(self): class TestRasterBasePlotSignatureContract: """M-1 regression: align abstract signature with concrete override.""" - def test_surface_reflectance_default_is_none(self): - """``RasterBase.plot.surface_reflectance`` defaults to ``None``. - - Test scenario: - Pre-PR-1 the abstract default was ``10000`` (Sentinel - reflectance constant). PR-1 changed it to ``None`` to mirror - the concrete ``Dataset.plot`` override and avoid a confusing - cross-class default mismatch. - """ - sig = inspect.signature(RasterBase.plot) - param = sig.parameters["surface_reflectance"] - assert param.default is None, f"Expected None default, got {param.default!r}" - def test_band_default_is_none(self): """``RasterBase.plot.band`` defaults to ``None``. @@ -485,18 +472,29 @@ def test_band_default_is_none(self): param = sig.parameters["band"] assert param.default is None, f"Expected None default, got {param.default!r}" - def test_rgb_default_is_none(self): - """``RasterBase.plot.rgb`` defaults to ``None``. + def test_rgb_options_default_is_none(self): + """``RasterBase.plot.rgb_options`` defaults to ``None``. Test scenario: - ``rgb=None`` lets the per-class facade decide whether to - apply an RGB heuristic — a concrete default would short- - circuit that decision on the abstract layer. + The loose ``rgb`` / ``surface_reflectance`` / ``cutoff`` kwargs were dropped + from the plot facades in favour of a single grouped ``rgb_options=`` dict; the + ABC declares it too, defaulting to ``None`` (no true-colour composite). """ sig = inspect.signature(RasterBase.plot) - param = sig.parameters["rgb"] + param = sig.parameters["rgb_options"] assert param.default is None, f"Expected None default, got {param.default!r}" + def test_loose_rgb_params_removed_from_signature(self): + """The deprecated loose Sentinel kwargs are gone from the ABC signature. + + Test scenario: + ``rgb`` / ``surface_reflectance`` / ``cutoff`` must no longer appear as + parameters — they are folded into ``rgb_options``. + """ + params = inspect.signature(RasterBase.plot).parameters + for name in ("rgb", "surface_reflectance", "cutoff"): + assert name not in params, f"{name!r} should have been removed from the ABC" + class TestNetCDFPlotPolicy: """Logic-only tests for :meth:`NetCDF.plot` (no cleopatra import). From 3b543a6ce7fcd5baf62a08825f30dd23e82df755 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:12:21 +0200 Subject: [PATCH 12/30] docs(plot): correct stale 'deprecated loose kwargs' docstrings on the plot facades Review M1: the loose styling kwargs were removed and now raise, but the colorbar param docstrings on Dataset.plot / DatasetCollection.plot and the RasterBase.plot ABC still described cbar_* / ticks_spacing as 'deprecated (still accepted, DeprecationWarning)', and the ABC still documented color_scale / gamma / bounds / display_cell_value / ... as valid **kwargs. Reword them to state the loose forms were removed and now raise ValueError, and redirect the ABC block to the typed color / contour / cells / data_style groups. --- src/pyramids/dataset/abstract_dataset.py | 39 +++++++++--------------- src/pyramids/dataset/collection.py | 7 ++--- src/pyramids/dataset/dataset.py | 8 ++--- 3 files changed, 21 insertions(+), 33 deletions(-) diff --git a/src/pyramids/dataset/abstract_dataset.py b/src/pyramids/dataset/abstract_dataset.py index 4c42570326..6577ee2374 100644 --- a/src/pyramids/dataset/abstract_dataset.py +++ b/src/pyramids/dataset/abstract_dataset.py @@ -1101,33 +1101,22 @@ def plot( Title size. The default is 15. colorbar (bool | ColorBar, optional): Colour-bar spec ``pyramids.plot.ColorBar(label=..., length=..., orientation=..., - label_size=..., label_rotation=..., label_location=..., ticks_spacing=...)`` - (cleopatra >= 0.28) — the complete, preferred replacement for the loose ``cbar_*`` / - ``ticks_spacing`` kwargs, which are deprecated (still accepted, but they emit a - ``DeprecationWarning``). ``False`` hides the bar, ``None`` uses the default. - color_scale (str, optional): - Color-scale mode. One of "linear", "power", "sym-lognorm", "boundary-norm", "midpoint" - (case-insensitive), or a ``cleopatra.styling.styles.ColorScale`` member. Integer codes are no - longer accepted. The default is "linear". - gamma (float, optional): - Exponent for the "power" color scale. The default is 1./2. - line_threshold (float, optional): - ``linthresh`` for the "sym-lognorm" color scale. The default is 0.0001. - line_scale (float, optional): - ``linscale`` for the "sym-lognorm" color scale. The default is 0.001. - bounds (List, optional): - A list of numbers used as discrete bounds for the "boundary-norm" color scale. Default is None. - midpoint (float, optional): - Midpoint value for the "midpoint" color scale. The default is 0. + label_size=..., label_rotation=..., label_location=..., ticks_spacing=...)``. + The loose ``cbar_*`` / ``ticks_spacing`` kwargs it replaces were removed — + passing one now raises a ``ValueError``. ``False`` hides the bar, ``None`` + uses the default. cmap (str, optional): Color style. The default is 'coolwarm_r'. - display_cell_value (bool, optional): - True if you want to display the values of the cells as text. - num_size (int, optional): - Size of the numbers plotted on top of each cell. The default is 8. - background_color_threshold (float | int, optional): - Threshold value. If the value of the cell is greater, the plotted numbers will be black; if smaller, - the plotted number will be white. If None, maxvalue/2 will be considered. The default is None. + + Colour-scale, contour, cell-value and data-style options moved onto the typed + render groups (all re-exported from ``pyramids.plot``): pass + ``color=ColorScaling(...)`` (linear / power / sym-log / boundary / midpoint + norm), ``contour=Contour(levels=..., ...)``, ``cells=CellValues(show=..., + size=..., background_threshold=...)`` and ``data_style=DataStyle(style=..., + hillshade=...)``. The loose forms they replace (``color_scale`` / ``gamma`` / + ``line_threshold`` / ``line_scale`` / ``bounds`` / ``midpoint`` / ``levels`` / + ``display_cell_value`` / ``num_size`` / ``background_color_threshold`` / + ``style`` / ``hillshade``) were removed and now raise a ``ValueError``. Returns: Tuple[Axes, Any]: diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index 38561daa12..41c528c406 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2501,10 +2501,9 @@ def plot( colorbar (bool or ColorBar, optional): Colour-bar spec ``pyramids.plot.ColorBar(label=…, length=…, orientation=…, label_size=…, label_rotation=…, label_location=…, - ticks_spacing=…)`` (cleopatra >= 0.28). The loose ``cbar_*`` / - ``ticks_spacing`` kwargs it replaces are deprecated — still accepted, - but they emit a :class:`DeprecationWarning`. ``False`` hides it, - ``None`` uses the default. Default ``None``. + ticks_spacing=…)``. The loose ``cbar_*`` / ``ticks_spacing`` kwargs it + replaces were removed — passing one now raises a :class:`ValueError`. + ``False`` hides it, ``None`` uses the default. Default ``None``. points (np.ndarray or PointOverlay, optional): Point overlay. A 3-column array ``(value, row, col)`` draws unstyled points; to style them pass a ``pyramids.plot.PointOverlay(points, diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index 872d8e6743..0aadbfe453 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -858,10 +858,10 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo is ``None``. Requires the ``[viz]`` extra. colorbar (bool or ColorBar, optional): Colour-bar spec. A ``pyramids.plot.ColorBar(label=…, length=…, - orientation=…, …)`` (cleopatra >= 0.28) draws a configured bar; the loose - ``cbar_*`` / ``ticks_spacing`` kwargs it replaces are deprecated (still - accepted, but they emit a :class:`DeprecationWarning`). ``False`` hides the - bar, ``None`` uses cleopatra's default. Default is ``None``. + orientation=…, …)`` draws a configured bar. The loose ``cbar_*`` / + ``ticks_spacing`` kwargs it replaces were removed — passing one now raises a + :class:`ValueError` pointing here. ``False`` hides the bar, ``None`` uses + cleopatra's default. Default is ``None``. points (np.ndarray or PointOverlay, optional): Point overlay. A 3-column array ``(value, row, col)`` draws unstyled points; pass a ``pyramids.plot.PointOverlay(points, color=…, size=…, …)`` From cc2297cdba991b10abb3fb2bacf705c46dc4bcc6 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:13:53 +0200 Subject: [PATCH 13/30] docs(plot): fix render_array Raises clause for the removed color_scale validation Review L1: the render_array docstring still advertised a ValueError 'if color_scale is not a recognised ColorScale value', but that pyramids-side validation was deleted in this branch. Reword to cover the cbar_* rejection and note the other removed loose kwargs raise cleopatra's own error. --- src/pyramids/dataset/_plot_helpers.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index 99e289fd49..e9867491cd 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -326,8 +326,11 @@ def render_array( required mode-specific argument is missing, if ``basemap`` is truthy and ``basemap_epsg`` is ``None``, if a cleopatra ``Basemap`` (or equivalent dict) is passed on the ``"facet"`` - path, or if ``color_scale`` is not a recognised - :class:`~cleopatra.styling.styles.ColorScale` value. + path, or if a removed loose colour-bar kwarg (``cbar_*`` / + ``ticks_spacing``) is passed. A removed loose styling kwarg + (``color_scale`` / ``style`` / ``levels`` / ``point_*`` / ...) + surfaces cleopatra's own "moved onto a grouped parameter object" + ``ValueError`` from the render call. Examples: - Single-slice plot path. Tagged ``+SKIP`` because the call From 9d83b8729550a13ab39caae711f13482427eb070 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:16:37 +0200 Subject: [PATCH 14/30] docs(plot): drop stale hoist comments referencing the deleted loose-kwarg translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review L2: the render-group hoist comments across the four plot facades said unset groups are dropped so they don't 'block render_array's loose-kwarg translation'. That translation (_migrate_deprecated_plot_specs / _build_grouped_render_specs) was removed in this branch; reword to the real reason — an unset group must not override cleopatra's backend default. --- src/pyramids/dataset/collection.py | 3 +-- src/pyramids/dataset/dataset.py | 4 ++-- src/pyramids/netcdf/netcdf.py | 4 ++-- src/pyramids/netcdf/ugrid/dataset.py | 4 ++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index 41c528c406..6c21da7f76 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2645,8 +2645,7 @@ def plot( if frame_label is not None: animate_extras["frame_label"] = frame_label # Fold the explicitly-set cleopatra render groups in; unset ones are dropped so - # they neither override the backend default nor block the loose-kwarg translation - # in render_array (an explicit group still wins there). + # they do not override cleopatra's backend default for that group. animate_extras.update( nonnull_group_kwargs( color=color, contour=contour, cells=cells, data_style=data_style diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index 0aadbfe453..2c02eb9bcd 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -964,8 +964,8 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo resolved_band, resolved_rgb = self._resolve_plot_band(band, rgb) # Spread the explicitly-set cleopatra render groups as their own ``**`` (not merged # into the typed ``**kwargs``, whose PlotKwargs TypedDict has no group keys); the - # unset ones are dropped so they neither override the backend default nor block the - # loose-kwarg translation in render_array (an explicit group still wins there). + # unset ones are dropped so they do not override cleopatra's backend default for + # that group. group_kwargs = nonnull_group_kwargs( color=color, contour=contour, cells=cells, data_style=data_style ) diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index ec8a1cb51e..7d12d15720 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -2156,8 +2156,8 @@ def plot( # type: ignore[override] # ``Dataset.plot`` / ``DatasetCollection.plot``; forward them through the same # ``**kwargs`` channel the multi-mode render path already consumes. Only inject # when set — the facet path rejects ``points`` outright, so a default ``None`` - # must not reach it, and an unset group must not block render_array's loose-kwarg - # translation (an explicit group still wins there). + # must not reach it, and an unset group must not override cleopatra's backend + # default for that group. if colorbar is not None: kwargs["colorbar"] = colorbar if points is not None: diff --git a/src/pyramids/netcdf/ugrid/dataset.py b/src/pyramids/netcdf/ugrid/dataset.py index 12f1658b94..9b5f3a0c03 100644 --- a/src/pyramids/netcdf/ugrid/dataset.py +++ b/src/pyramids/netcdf/ugrid/dataset.py @@ -914,8 +914,8 @@ def plot( # but have no meaning for a mesh (no point overlay; the renderer is fixed to # tripcolor/tricontour), so they are accepted and ignored. ``colorbar`` and the # typed render groups (``color`` / ``contour`` / ``data_style``) map onto the mesh - # backend and are forwarded only when set (so cleopatra's defaults and the - # loose-kwarg translation are preserved otherwise). + # backend and are forwarded only when set (so cleopatra's backend defaults are + # preserved otherwise). if colorbar is not None: kwargs["colorbar"] = colorbar kwargs.update( From 34794fc1fec588865dbc81efef0a0b63e4c1414e Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:19:44 +0200 Subject: [PATCH 15/30] test(plot): assert typed color group is forwarded, drop phantom loose-precedence tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review L3: the two 'explicit color wins over loose color_scale' tests passed a dead color_scale= kwarg alongside the group and asserted only that the group is forwarded — a vacuous precedence claim now that the loose->group translation is gone (against real cleopatra the loose color_scale would itself raise). Reword the render_array test to a plain color-forwarding assertion (no color_scale=) and delete the mesh one (redundant with test_color_and_data_style_reach_mesh_glyph_plot). --- tests/dataset/plot/test_plot_engine.py | 11 +++++------ tests/ugrid/test_plot.py | 10 ---------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index 4c97e4bd45..1620f3e696 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -775,12 +775,12 @@ class TestRenderArrayGroupParams: kwargs (``color_scale`` / ``style`` / ...). """ - def test_explicit_color_group_wins_over_loose_color_scale(self): - """An explicit ``color=ColorScaling`` overrides a loose ``color_scale``. + def test_explicit_color_group_reaches_render_call(self): + """An explicit ``color=ColorScaling`` is forwarded to the render call. Test scenario: - Passing both the typed ``color`` and the loose ``color_scale`` must forward - the explicit spec (the loose translation only fills an unset group). + The typed ``color`` group must reach ``ArrayGlyph.plot`` verbatim (the loose + ``color_scale`` kwarg it replaces was removed and now raises). """ from pyramids.plot import ColorScaling @@ -794,9 +794,8 @@ def test_explicit_color_group_wins_over_loose_color_scale(self): extent=[0.0, 0.0, 1.0, 1.0], mode="plot", color=explicit, - color_scale="linear", ) - assert plot.get("color") is explicit, "explicit color must win over color_scale" + assert plot.get("color") is explicit, "explicit color must reach the render call" def test_explicit_groups_reach_render_call(self): """Explicit ``contour`` / ``cells`` / ``data_style`` reach the render call. diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index 0309b5e647..3999eefcdc 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -478,13 +478,3 @@ def test_color_and_data_style_reach_mesh_glyph_plot(self): assert kw.get("color") is color, "explicit color must reach MeshGlyph.plot" assert kw.get("data_style") is data_style, "explicit data_style must reach it" - def test_explicit_color_wins_over_loose_color_scale(self): - """An explicit ``color`` overrides the loose ``color_scale`` on the mesh path.""" - from cleopatra.styling.scaling import ColorScaling - - color = ColorScaling.power(gamma=0.6) - with patch.object(MeshGlyph, "plot") as mock_plot: - self._dataset("face").plot("depth", color=color, color_scale="linear") - assert mock_plot.call_args.kwargs.get("color") is color, ( - "explicit color must win over loose color_scale" - ) From df83967354611522f2e5936a85900cb4446dee99 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:23:35 +0200 Subject: [PATCH 16/30] test(plot): correct stale color_scale / rgb_options test docstrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review L4 + N1: two color_scale reject-test docstrings described removed behavior (a pyramids-side value-level validation / cleopatra 'validating against the ColorScale enum') — cleopatra 0.30 rejects the color_scale *key* regardless of value. Reword both. Also fix a test docstring that still referenced the renamed _merge_rgb_options (now _unpack_rgb_options). --- tests/dataset/plot/test_plot_dataset.py | 11 ++++++----- tests/dataset/plot/test_plot_facade.py | 11 +++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/dataset/plot/test_plot_dataset.py b/tests/dataset/plot/test_plot_dataset.py index 553020426b..5f63264511 100644 --- a/tests/dataset/plot/test_plot_dataset.py +++ b/tests/dataset/plot/test_plot_dataset.py @@ -141,12 +141,13 @@ def histogram(self, bins=15): @pytest.mark.plot def test_invalid_color_scale_raises(self, src: Dataset): - """An unsupported ``color_scale`` fails fast with a clear message. + """A loose ``color_scale`` kwarg is rejected with a clear ``ValueError``. Test scenario: - ``color_scale="bogus"`` is rejected before any rendering work, - with a pyramids-side ``ValueError`` that names the offending - value (and lists the valid options). + cleopatra 0.30 moved the colour scale onto the ``color=ColorScaling`` group + and rejects the ``color_scale`` *key* itself (regardless of value), so + ``color_scale="bogus"`` raises a ``ValueError`` naming ``color_scale``, which + pyramids surfaces unchanged. """ dataset = Dataset(src) with pytest.raises(ValueError, match=r"color_scale"): @@ -585,7 +586,7 @@ def test_rgb_surface_reflectance_normalisation(self, tmp_path): @pytest.mark.plot def test_rgb_options_unknown_key_raises(self, tmp_path): - """An unknown `rgb_options` key raises (delegated to `_merge_rgb_options`).""" + """An unknown `rgb_options` key raises (delegated to `_unpack_rgb_options`).""" cube = self._rgb_cube(tmp_path, n_times=2) with pytest.raises(ValueError, match=r"Unknown keys in `rgb_options`"): cube.plot(rgb_options={"bogus": 1}) diff --git a/tests/dataset/plot/test_plot_facade.py b/tests/dataset/plot/test_plot_facade.py index 65a831d1a8..333651dfee 100644 --- a/tests/dataset/plot/test_plot_facade.py +++ b/tests/dataset/plot/test_plot_facade.py @@ -85,14 +85,13 @@ def test_color_scale_integer_codes_rejected(self, color_scale): """Legacy integer ``color_scale`` codes raise a clear ``ValueError``. Args: - color_scale: A legacy integer code that older releases - accepted but cleopatra's ``ColorScale`` enum rejects. + color_scale: A legacy integer code that older releases accepted. Test scenario: - cleopatra now validates ``color_scale`` against the - ``ColorScale`` StrEnum and raises ``ValueError`` for anything - that is not one of the documented aliases (or a ``ColorScale`` - member). Confirm pyramids surfaces that error unchanged. + cleopatra 0.30 moved the colour scale onto the ``color=ColorScaling`` group + and rejects the loose ``color_scale`` *key* regardless of its value (an + integer code, a string alias, or anything else), so pyramids surfaces that + ``ValueError`` unchanged. """ rng = np.random.default_rng(7) arr = rng.random((5, 5)).astype("float32") From 42b10f1fb56b2bff738795c44c44f281492144a1 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:26:52 +0200 Subject: [PATCH 17/30] docs(analysis): reflow the Analysis.plot kwargs table into a wrappable bullet list Review N2: the reworded **kwargs markdown table rows ran 122-364 chars (the 16-space docstring indent + fixed column widths push every row past the repo's 120-char limit, which allows no table-row exception). Convert the table to a bullet list that wraps at 120, and abbreviate the PointOverlay / ColorBar constructor lists (the full param lists live on the facade Args). --- src/pyramids/dataset/engines/analysis.py | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/pyramids/dataset/engines/analysis.py b/src/pyramids/dataset/engines/analysis.py index 925eec25e8..03063c11e2 100644 --- a/src/pyramids/dataset/engines/analysis.py +++ b/src/pyramids/dataset/engines/analysis.py @@ -2018,16 +2018,22 @@ def plot( ``background_color_threshold`` / ``style`` / ``hillshade`` / ``point_*`` / ``cbar_*`` / ``ticks_spacing`` — are no longer accepted and now raise. The remaining still-loose kwargs pass through to cleopatra: - | Parameter | Type | Description | - |-----------------------------|---------------------|-------------| - | `points` | array \\| PointOverlay | Point overlay. A bare 3-column array (value, row, col) draws unstyled points; pass `pyramids.plot.PointOverlay(points, color=..., size=..., label_color=..., label_size=...)` to style them. | - | `cmap` | str, optional | Color map style. Default is `'coolwarm_r'`. | - | `figsize` | tuple, optional | Figure size. Default is `(8, 8)`. | - | `title` | str, optional | Title of the plot. Default is `'Total Discharge'`. | - | `title_size` | int, optional | Title size. Default is `15`. | - | `add_colorbar` | bool, optional | Whether to draw the colour bar. Default is `True`. When `False`, no colorbar is created and the returned glyph's `cbar` is `None`. | - | `colorbar` | bool \\| ColorBar, optional | Colour-bar spec `pyramids.plot.ColorBar(label=…, length=…, orientation=…, label_size=…, label_rotation=…, label_location=…, ticks_spacing=…)` — the complete replacement for the removed loose `cbar_*` / `ticks_spacing` kwargs. `False` hides it, `None` uses the default. | - | `full_bleed` | bool \\| str, optional | Chrome-free layout: drop axes/margins so the array fills the figure. Default `False`. | + + - `points` (array | PointOverlay): Point overlay. A bare 3-column array + `(value, row, col)` draws unstyled points; pass a + `pyramids.plot.PointOverlay(points, color=..., size=..., ...)` to style them. + - `cmap` (str, optional): Color map style. Default is `'coolwarm_r'`. + - `figsize` (tuple, optional): Figure size. Default is `(8, 8)`. + - `title` (str, optional): Title of the plot. Default is `'Total Discharge'`. + - `title_size` (int, optional): Title size. Default is `15`. + - `add_colorbar` (bool, optional): Whether to draw the colour bar. Default is + `True`; when `False` the returned glyph's `cbar` is `None`. + - `colorbar` (bool | ColorBar, optional): Colour-bar spec + `pyramids.plot.ColorBar(label=..., orientation=..., ...)` — replaces the + removed loose `cbar_*` / `ticks_spacing` kwargs. `False` hides it, `None` + uses the default. + - `full_bleed` (bool | str, optional): Chrome-free layout: drop axes/margins + so the array fills the figure. Default `False`. Returns: ArrayGlyph: A cleopatra ``ArrayGlyph`` wrapping the rendered figure. The underlying matplotlib From d7d90baba337848f3851f33db318c8675283daf4 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 22:28:08 +0200 Subject: [PATCH 18/30] refactor(netcdf): defer the cleopatra params import to the group-building branch Review N3: the cleopatra styling-params import in the NetCDF colour-kwargs builder ran on every plot even when no levels/style/hillshade was set. Push each import into the branch that actually builds a Contour / DataStyle, so a plain NetCDF plot never imports it. --- src/pyramids/netcdf/_plot.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pyramids/netcdf/_plot.py b/src/pyramids/netcdf/_plot.py index 3c2eac445f..0e96ebf145 100644 --- a/src/pyramids/netcdf/_plot.py +++ b/src/pyramids/netcdf/_plot.py @@ -516,9 +516,11 @@ def _build_render_kwargs( # cleopatra 0.30 moved ``levels`` / ``style`` / ``hillshade`` off the loose kwargs # onto the typed render groups, so build them from the ColorOpts bag here (a no-op # ``style=None`` / falsy ``hillshade`` is dropped, matching the render-group rules). - from cleopatra.styling.params import Contour, DataStyle - + # The cleopatra import is deferred to the branch that actually builds a group, so a + # plain NetCDF plot (no levels/style/hillshade) never imports it. if colour.levels is not None: + from cleopatra.styling.params import Contour + out["contour"] = Contour(levels=colour.levels) data_style_fields: dict[str, Any] = {} if colour.style is not None: @@ -526,6 +528,8 @@ def _build_render_kwargs( if colour.hillshade is not None and colour.hillshade is not False: data_style_fields["hillshade"] = colour.hillshade if data_style_fields: + from cleopatra.styling.params import DataStyle + out["data_style"] = DataStyle(**data_style_fields) # Curvilinear coord resolution. Priority (highest first): From 1ffde3bac91279ed9dea4636d12c991f4dea79a1 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:05:53 +0200 Subject: [PATCH 19/30] fix(netcdf): let an explicit hoisted contour/data_style win over ColorOpts Review L1: NetCDF.plot exposes both the hoisted contour=/data_style= groups and the older ColorOpts(levels=/style=/hillshade=) bag. _build_render_kwargs unconditionally reassigned out['contour']/out['data_style'] from ColorOpts, silently discarding the caller's explicit group (contour=Contour(levels=[1,2,3]) + ColorOpts(levels=9) forwarded levels=9). Guard the ColorOpts-derived build on the key being absent so the explicit group wins, and add a regression test. --- src/pyramids/netcdf/_plot.py | 8 ++++--- .../netcdf/plot/test_hoisted_render_params.py | 23 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/pyramids/netcdf/_plot.py b/src/pyramids/netcdf/_plot.py index 0e96ebf145..4c64fb0cea 100644 --- a/src/pyramids/netcdf/_plot.py +++ b/src/pyramids/netcdf/_plot.py @@ -517,8 +517,10 @@ def _build_render_kwargs( # onto the typed render groups, so build them from the ColorOpts bag here (a no-op # ``style=None`` / falsy ``hillshade`` is dropped, matching the render-group rules). # The cleopatra import is deferred to the branch that actually builds a group, so a - # plain NetCDF plot (no levels/style/hillshade) never imports it. - if colour.levels is not None: + # plain NetCDF plot (no levels/style/hillshade) never imports it. An explicit hoisted + # ``contour=`` / ``data_style=`` (already in ``out`` via ``base_kwargs``) wins — the + # ColorOpts-derived group only fills the slot when the caller left it unset. + if colour.levels is not None and "contour" not in out: from cleopatra.styling.params import Contour out["contour"] = Contour(levels=colour.levels) @@ -527,7 +529,7 @@ def _build_render_kwargs( data_style_fields["style"] = colour.style if colour.hillshade is not None and colour.hillshade is not False: data_style_fields["hillshade"] = colour.hillshade - if data_style_fields: + if data_style_fields and "data_style" not in out: from cleopatra.styling.params import DataStyle out["data_style"] = DataStyle(**data_style_fields) diff --git a/tests/netcdf/plot/test_hoisted_render_params.py b/tests/netcdf/plot/test_hoisted_render_params.py index 8f7a150331..072b932978 100644 --- a/tests/netcdf/plot/test_hoisted_render_params.py +++ b/tests/netcdf/plot/test_hoisted_render_params.py @@ -206,3 +206,26 @@ def test_unset_groups_not_forwarded(self): forwarded = mock_plot.call_args.kwargs for name in ("color", "contour", "cells", "data_style"): assert name not in forwarded, f"unset {name} must not be forwarded" + + def test_explicit_contour_wins_over_coloropts_levels(self): + """An explicit ``contour=`` beats a ``ColorOpts(levels=...)`` (no silent override). + + Test scenario: + ``NetCDF.plot`` exposes both the hoisted ``contour=`` group and the older + ``ColorOpts(levels=...)`` bag. When both are given the caller's explicit + ``contour`` must win — the ColorOpts-derived one only fills an unset slot. + """ + from cleopatra.styling.params import Contour + + from pyramids.netcdf import ColorOpts + + nc = make_plot_3d_nc() + var = nc.get_variable("t2m") + explicit = Contour(levels=[1, 2, 3]) + with patch.object(type(var.analysis), "plot", autospec=True) as mock_plot: + mock_plot.return_value = "ok" + nc.plot(variable="t2m", contour=explicit, colour=ColorOpts(levels=9)) + forwarded = mock_plot.call_args.kwargs.get("contour") + assert forwarded is explicit and forwarded.levels == [1, 2, 3], ( + f"explicit contour must win over ColorOpts levels; got {forwarded}" + ) From 0c4bdefa37bec1ffac151e3bc11745669c5b59b5 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:07:36 +0200 Subject: [PATCH 20/30] test(ugrid): assert data_style reaches _mesh_render, not the removed loose style/hillshade Review L2: test_style_forwarded_to_mesh_render still passed loose style=/hillshade= and mocked _mesh_render, masking that the un-mocked UgridDataset.plot(style=...) now raises end-to-end. Convert it to the typed data_style=DataStyle group like its siblings. --- tests/ugrid/test_plot.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index 3999eefcdc..60ad49cfc3 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -329,16 +329,21 @@ def test_style_and_hillshade_reach_mesh_glyph_plot(self): assert data_style.style == "flow_accumulation" assert data_style.hillshade is True - def test_style_forwarded_to_mesh_render(self): - """``UgridDataset.plot`` forwards ``style`` / ``hillshade`` to the helper.""" + def test_data_style_forwarded_to_mesh_render(self): + """``UgridDataset.plot`` forwards a ``data_style=DataStyle`` to the helper. + + The loose ``style`` / ``hillshade`` kwargs were removed (they now raise); the + typed ``data_style`` group is what reaches ``_mesh_render``. + """ + from cleopatra.styling.params import DataStyle + ds = self._dataset("face") + data_style = DataStyle(style="topography", hillshade=True) with patch( "pyramids.netcdf.ugrid.dataset._mesh_render", return_value="sentinel" ) as mock_render: - ds.plot("depth", style="topography", hillshade=True) - kw = mock_render.call_args.kwargs - assert kw.get("style") == "topography" - assert kw.get("hillshade") is True + ds.plot("depth", data_style=data_style) + assert mock_render.call_args.kwargs.get("data_style") is data_style def test_falsy_hillshade_is_dropped(self): """``hillshade=False`` is dropped — no ``DataStyle`` is built for it.""" From d0ae40123533e041071e999b1436e26361a72efa Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:13:55 +0200 Subject: [PATCH 21/30] docs(plot): standardise cleopatra version references on the >=0.30 pin Review L3: the [viz] floor is cleopatra >=0.30.0, but the pyramids.plot module docstring and its OptionalPackageDoesNotExist upgrade hint (plus the matching test) still said '>= 0.29', under-specifying the real requirement. Update those to 0.30, and drop the now -redundant '(cleopatra >= 0.28/0.29)' since-annotations from the feature docstrings (the 0.30 floor guarantees them). Also reword a stale render_array comment that still described the removed cbar_* folding (cbar_* are now rejected up front, not folded). --- src/pyramids/dataset/_plot_helpers.py | 8 ++++---- src/pyramids/dataset/collection.py | 4 ++-- src/pyramids/dataset/dataset.py | 2 +- src/pyramids/dataset/engines/analysis.py | 2 +- src/pyramids/netcdf/netcdf.py | 4 ++-- src/pyramids/netcdf/ugrid/plot.py | 4 ++-- src/pyramids/plot.py | 4 ++-- tests/dataset/plot/test_plot_specs.py | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/pyramids/dataset/_plot_helpers.py b/src/pyramids/dataset/_plot_helpers.py index e9867491cd..ef98f8c576 100644 --- a/src/pyramids/dataset/_plot_helpers.py +++ b/src/pyramids/dataset/_plot_helpers.py @@ -312,8 +312,8 @@ def render_array( ``basemap`` is truthy and this is ``None`` the helper raises :class:`ValueError`. **kwargs: Forwarded to the cleopatra entry point selected by - ``mode`` (including cleopatra >= 0.28 render params such as - ``colorbar=`` / ``full_bleed=``). + ``mode`` (including render params such as ``colorbar=`` / + ``full_bleed=``). Returns: The result object cleopatra returns for that mode — typically a @@ -567,8 +567,8 @@ def render_array( # ``title`` arg is not ``None``, so a constructor-set title survives and # routing it to the constructor (via ``option_keys()``) is correct. # ``kind`` is force-routed to the render call. The loose cbar_* kwargs no longer - # need force-routing: every mode (incl. facet, on cleopatra >= 0.29) folds them - # into ``colorbar=ColorBar(...)`` above, so they never reach the split. + # need force-routing: they are rejected up front by ``_reject_replaced_cbar_kwargs`` + # (``colorbar=ColorBar`` is the only colour-bar surface), so they never reach the split. RENDER_ONLY_OVERRIDES = {"kind"} # Reuse the option set resolved for the style/hillshade guard above — it is # cleopatra's declared constructor options and does not change within a call. diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index 6c21da7f76..8c806951c4 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2488,13 +2488,13 @@ def plot( Reference layer under the animation, dispatched by type. ``True`` or a tile-provider string (e.g. ``"CartoDB.Positron"``) overlays a pyramids web-tile basemap; a ``pyramids.plot.Basemap(relief=..., - features=...)`` (cleopatra >= 0.28) draws a shaded-relief / + features=...)`` draws a shaded-relief / coastline layer instead. The base raster's CRS is supplied automatically. Default ``None`` (no basemap). Requires the ``[viz]`` extra. frame_label (FrameLabel, optional): Typed per-frame label spec ``pyramids.plot.FrameLabel(...)`` - (cleopatra >= 0.28) that styles the animation's frame caption + that styles the animation's frame caption (colour, size, placement). ``animation_axis_values`` sets the label *text* per frame; ``frame_label`` styles it. Default ``None`` (cleopatra's default frame label). diff --git a/src/pyramids/dataset/dataset.py b/src/pyramids/dataset/dataset.py index 2c02eb9bcd..026d1b3565 100644 --- a/src/pyramids/dataset/dataset.py +++ b/src/pyramids/dataset/dataset.py @@ -852,7 +852,7 @@ def plot( # type: ignore[override] # narrows **kwargs to cleopatra's typed Plo basemap (bool, str, or Basemap, optional): Reference layer, dispatched by type. ``True`` or a tile-provider string (e.g. ``"CartoDB.Positron"``) overlays a pyramids web-tile basemap. A - ``pyramids.plot.Basemap(relief=..., features=...)`` (cleopatra >= 0.28) + ``pyramids.plot.Basemap(relief=..., features=...)`` draws a shaded-relief / coastline layer instead. Passing a ``dict`` here is a deprecated alias for ``Basemap`` (emits a ``DeprecationWarning``). Default is ``None``. Requires the ``[viz]`` extra. diff --git a/src/pyramids/dataset/engines/analysis.py b/src/pyramids/dataset/engines/analysis.py index 03063c11e2..39e182b2b1 100644 --- a/src/pyramids/dataset/engines/analysis.py +++ b/src/pyramids/dataset/engines/analysis.py @@ -2003,7 +2003,7 @@ def plot( basemap (bool, str, or Basemap, optional): Reference layer under the plot, dispatched by type. ``True`` or a tile-provider string (e.g. "CartoDB.Positron") draws a pyramids web-tile basemap. A - ``pyramids.plot.Basemap(relief=..., features=...)`` (cleopatra >= 0.28) draws a + ``pyramids.plot.Basemap(relief=..., features=...)`` draws a shaded-relief / coastline reference layer instead. Default is None (no basemap). Requires the [viz] extra (mercantile, xyzservices, Pillow). A ``Basemap`` is not supported on the faceted path. diff --git a/src/pyramids/netcdf/netcdf.py b/src/pyramids/netcdf/netcdf.py index 7d12d15720..a29513684c 100644 --- a/src/pyramids/netcdf/netcdf.py +++ b/src/pyramids/netcdf/netcdf.py @@ -1761,7 +1761,7 @@ def plot( # type: ignore[override] basemap (bool, str, or Basemap, optional): Reference layer, dispatched by type. ``True`` or a named contextily tile provider overlays a pyramids web-tile basemap; a - ``pyramids.plot.Basemap`` (cleopatra >= 0.28) draws a relief/features + ``pyramids.plot.Basemap`` draws a relief/features layer instead (not supported on the faceted path). Defaults to None. exclude_value (Any, optional): Pixel value to mask out before plotting. Defaults to None. @@ -1773,7 +1773,7 @@ def plot( # type: ignore[override] Figure size in inches. Defaults to None. colorbar (bool or ColorBar, optional): Colour-bar spec. A ``pyramids.plot.ColorBar(label=…, …)`` - (cleopatra >= 0.28) draws a configured bar; ``False`` hides it and + draws a configured bar; ``False`` hides it and ``None`` uses cleopatra's default. Only forwarded when set. Default is ``None``. points (np.ndarray or PointOverlay, optional): diff --git a/src/pyramids/netcdf/ugrid/plot.py b/src/pyramids/netcdf/ugrid/plot.py index 68246217b6..050a75652f 100644 --- a/src/pyramids/netcdf/ugrid/plot.py +++ b/src/pyramids/netcdf/ugrid/plot.py @@ -117,8 +117,8 @@ def plot_mesh_data( Defaults to `"none"`. colorbar: Colour-bar control. `True` (default) draws the default bar, `False`/`None` hides it, or pass a `pyramids.plot.ColorBar(...)` to - style it (caption / placement / sizing) — cleopatra >= 0.29 renders - the typed spec on `MeshGlyph` just like `ArrayGlyph`. + style it (caption / placement / sizing) — cleopatra renders the typed + spec on `MeshGlyph` just like `ArrayGlyph`. title: Plot title string. Defaults to None (no title). **kwargs: Additional keyword arguments forwarded to `MeshGlyph.plot`. Common options include diff --git a/src/pyramids/plot.py b/src/pyramids/plot.py index 3f485c2e72..a7a3765a09 100644 --- a/src/pyramids/plot.py +++ b/src/pyramids/plot.py @@ -1,7 +1,7 @@ """Typed cleopatra plot specs, re-exported for pyramids. pyramids' ``plot`` and ``animate`` methods accept and forward the cleopatra -plotting spec objects (the ``[viz]`` extra requires cleopatra >= 0.29, which +plotting spec objects (the ``[viz]`` extra requires cleopatra >= 0.30, which carries the complete spec API). Import them from here so a call site never has to reach into ``cleopatra`` directly: @@ -107,7 +107,7 @@ def __getattr__(name: str) -> Any: # branded upgrade hint instead of a bare AttributeError. raise OptionalPackageDoesNotExist( f"cleopatra is installed but too old to provide `{attribute}`. The " - "pyramids plotting specs require cleopatra >= 0.29; upgrade with: " + "pyramids plotting specs require cleopatra >= 0.30; upgrade with: " "pip install -U 'pyramids-gis[viz]'." ) from exc return spec diff --git a/tests/dataset/plot/test_plot_specs.py b/tests/dataset/plot/test_plot_specs.py index efff002c14..b2a62518c8 100644 --- a/tests/dataset/plot/test_plot_specs.py +++ b/tests/dataset/plot/test_plot_specs.py @@ -84,5 +84,5 @@ class _OldCleopatraModule: with patch( "pyramids.plot.require_optional", return_value=_OldCleopatraModule() ): - with pytest.raises(OptionalPackageDoesNotExist, match="0.29"): + with pytest.raises(OptionalPackageDoesNotExist, match="0.30"): plot_specs.ColorBar From c9ffb2aaeb656e2be1a5cc20685d53882231eae0 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:15:46 +0200 Subject: [PATCH 22/30] fix(netcdf): drop the dead levels entry from _ANIMATE_DROP_KWARGS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review L4: ColorOpts.levels is converted to contour=Contour(...) in _build_render_kwargs before the animate drop runs, so the 'levels' entry in _ANIMATE_DROP_KWARGS never matches that path — it only caught a stray loose levels= kwarg, silently stripping it on the animate path while the static path raises. Remove it so a removed loose levels= is rejected consistently across modes. --- src/pyramids/netcdf/_plot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyramids/netcdf/_plot.py b/src/pyramids/netcdf/_plot.py index 4c64fb0cea..2a4392c2f4 100644 --- a/src/pyramids/netcdf/_plot.py +++ b/src/pyramids/netcdf/_plot.py @@ -106,7 +106,6 @@ def _reject_forbidden_kwargs(kwargs: dict[str, Any]) -> None: "extend", "cbar_kwargs", "aspect", - "levels", "center", "norm", "robust", From b50f0625f107efd5c2d2400fbf107cc12c010552 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:17:36 +0200 Subject: [PATCH 23/30] docs(analysis): wrap the over-120 ArrayGlyph doc-URL line Review N1: the ArrayGlyph reference-link line grew to 146 chars when the module path lengthened in the restructure (cleopatra.glyphs.gridded.array_glyph). Reflow the prose and drop the deep method anchor so the line fits the 120-char limit. --- src/pyramids/dataset/engines/analysis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pyramids/dataset/engines/analysis.py b/src/pyramids/dataset/engines/analysis.py index 39e182b2b1..3cbc506e78 100644 --- a/src/pyramids/dataset/engines/analysis.py +++ b/src/pyramids/dataset/engines/analysis.py @@ -1962,8 +1962,9 @@ def plot( ``color=ColorScaling.boundary(bounds=...)`` so each pixel value shows its own colour (#913). An explicit ``cmap`` / ``color`` opts out. - The plot function uses the `cleopatra` as a backend to plot the raster data, for more information check - [ArrayGlyph](https://serapeum-org.github.io/cleopatra/latest/api/array-glyph-class/#cleopatra.glyphs.gridded.array_glyph.ArrayGlyph.plot). + The plot function uses `cleopatra` as a backend to plot the raster data; for more + information see the + [ArrayGlyph reference](https://serapeum-org.github.io/cleopatra/latest/api/array-glyph-class/). Implementation note: this method is a thin caller around the shared :func:`pyramids.dataset._plot_helpers.render_array` From bf20b358b863c5ca32e8065318c5fbc300f42f53 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:27:10 +0200 Subject: [PATCH 24/30] test(plot): split composite assertions (SonarCloud python:S9073) SonarCloud S9073: split 'assert a and b' composite assertions into separate asserts in the render-routing and the ColorOpts-precedence tests, so a failure pinpoints which half broke. --- tests/dataset/plot/test_plot_engine.py | 10 +++++----- tests/netcdf/plot/test_hoisted_render_params.py | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index 1620f3e696..555168cb4c 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -298,12 +298,12 @@ def test_split_is_driven_by_option_keys(self): add_colorbar=False, kind="imshow", ) - assert "add_colorbar" in ctor and "add_colorbar" not in plot, ( - f"`add_colorbar` is an option_keys() ctor option; ctor={ctor}" - ) - assert "kind" in plot and "kind" not in ctor, ( - f"`kind` must be force-routed to the render call; plot={plot}" + assert "add_colorbar" in ctor, f"`add_colorbar` is a ctor option; ctor={ctor}" + assert "add_colorbar" not in plot, ( + f"`add_colorbar` must not also reach the render call; plot={plot}" ) + assert "kind" in plot, f"`kind` must be force-routed to the render call; plot={plot}" + assert "kind" not in ctor, f"`kind` must not stay on the ctor; ctor={ctor}" def test_loose_cbar_kwarg_rejected(self): """A loose ``cbar_*`` kwarg is rejected in favour of the typed ``ColorBar`` spec. diff --git a/tests/netcdf/plot/test_hoisted_render_params.py b/tests/netcdf/plot/test_hoisted_render_params.py index 072b932978..dc53fa33c0 100644 --- a/tests/netcdf/plot/test_hoisted_render_params.py +++ b/tests/netcdf/plot/test_hoisted_render_params.py @@ -226,6 +226,7 @@ def test_explicit_contour_wins_over_coloropts_levels(self): mock_plot.return_value = "ok" nc.plot(variable="t2m", contour=explicit, colour=ColorOpts(levels=9)) forwarded = mock_plot.call_args.kwargs.get("contour") - assert forwarded is explicit and forwarded.levels == [1, 2, 3], ( - f"explicit contour must win over ColorOpts levels; got {forwarded}" + assert forwarded is explicit, f"explicit contour must win; got {forwarded}" + assert forwarded.levels == [1, 2, 3], ( + f"the winning contour must keep its levels; got {forwarded.levels}" ) From 928b1f317b4d23b1656f146e33641490e8ff03e4 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:27:55 +0200 Subject: [PATCH 25/30] test(netcdf): isolate the throwing call in the facet points-rejection test (SonarCloud python:S5778) SonarCloud S5778: the pytest.raises block also constructed FacetSpec/_points(), so more than the intended call could throw. Build them before the with-block so only nc.plot can raise inside it. --- tests/netcdf/plot/test_hoisted_render_params.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/netcdf/plot/test_hoisted_render_params.py b/tests/netcdf/plot/test_hoisted_render_params.py index dc53fa33c0..0c3b71f715 100644 --- a/tests/netcdf/plot/test_hoisted_render_params.py +++ b/tests/netcdf/plot/test_hoisted_render_params.py @@ -169,8 +169,9 @@ def test_facet_with_explicit_points_raises(self): raises ``ValueError`` naming the offending keyword. """ nc = make_plot_3d_nc(n_times=4) + facet, pts = FacetSpec(col="time"), _points() with pytest.raises(ValueError, match="points") as exc_info: - nc.plot(variable="t2m", facet=FacetSpec(col="time"), points=_points()) + nc.plot(variable="t2m", facet=facet, points=pts) assert "points" in str(exc_info.value), f"unexpected error: {exc_info.value}" From cc641cb9a0c55e747ade73c26d0699431e54523a Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Tue, 11 Aug 2026 23:30:43 +0200 Subject: [PATCH 26/30] refactor(collection): extract RGB-animation validation to cut plot complexity (SonarCloud python:S3776) SonarCloud S3776: DatasetCollection.plot's Cognitive Complexity was 16 (> 15) after the render-group hoisting. Move the four RGB band-layout guards (issue #538) into a _validate_rgb_animation helper, dropping the branches out of plot. Behavior is unchanged (the warning stacklevel is bumped to 3 to keep pointing at the caller's plot() call). --- src/pyramids/dataset/collection.py | 62 +++++++++++++++++++----------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index 8c806951c4..564e4db413 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2425,6 +2425,42 @@ def iloc(self, i: int) -> Dataset: """ return self._dataset_at(i) + def _validate_rgb_animation( + self, rgb: list[int], exclude_value: Any | None + ) -> None: + """Validate the ``rgb`` band layout for an RGB time-lapse (issue #538). + + Args: + rgb: The band indices requested for the true-colour composite. + exclude_value: The caller's ``exclude_value`` (ignored for RGB frames; + a non-``None`` value warns because true-colour frames are not masked). + + Raises: + ValueError: If ``rgb`` is not 3 or 4 indices, has a negative index, or asks + for more bands than the collection's datasets carry. + """ + if len(rgb) not in (3, 4): + raise ValueError( + f"rgb must list 3 band indices (RGB) or 4 (RGBA), got " + f"{rgb!r} with {len(rgb)} entries." + ) + if min(rgb) < 0: + raise ValueError(f"rgb band indices must be non-negative, got {rgb!r}.") + if exclude_value is not None: + warnings.warn( + "exclude_value is ignored for RGB animations; true-colour " + "frames are not masked. Drop exclude_value, or render a " + "single band to mask by no-data.", + UserWarning, + stacklevel=3, + ) + needed = max(rgb) + 1 + if self.base.band_count < needed: + raise ValueError( + f"rgb={rgb} needs at least {needed} bands, but the " + f"collection's datasets have {self.base.band_count}." + ) + def plot( self, band: int = 0, @@ -2659,30 +2695,10 @@ def plot( if rgb is not None: # RGB time-lapse: read the FULL multi-band array per timestep and # stack to (time, bands, rows, cols); render_array composites the - # true-colour frames. Guard the band layout here so a misshapen - # ``rgb`` raises a clear error instead of cleopatra silently + # true-colour frames. The band layout is validated in a helper so a + # misshapen ``rgb`` raises a clear error instead of cleopatra silently # collapsing the time axis into the colour channels (issue #538). - if len(rgb) not in (3, 4): - raise ValueError( - f"rgb must list 3 band indices (RGB) or 4 (RGBA), got " - f"{rgb!r} with {len(rgb)} entries." - ) - if min(rgb) < 0: - raise ValueError(f"rgb band indices must be non-negative, got {rgb!r}.") - if exclude_value is not None: - warnings.warn( - "exclude_value is ignored for RGB animations; true-colour " - "frames are not masked. Drop exclude_value, or render a " - "single band to mask by no-data.", - UserWarning, - stacklevel=2, - ) - needed = max(rgb) + 1 - if self.base.band_count < needed: - raise ValueError( - f"rgb={rgb} needs at least {needed} bands, but the " - f"collection's datasets have {self.base.band_count}." - ) + self._validate_rgb_animation(rgb, exclude_value) data = np.stack([ds.read_array(band=None) for ds in self.datasets], axis=0) return render_array( arr=data, From 1e3db721a8eeba2374bf32d78185603953919ad6 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Wed, 12 Aug 2026 17:30:18 +0200 Subject: [PATCH 27/30] docs(examples): migrate notebooks to cleopatra 0.30 grouped plot API The dataset-collection and ugrid-river-channel example notebooks still used the pre-0.30 cleopatra surface, which nbval runs in the main-package CI job: - dataset_collection: import FrameLabel from the moved cleopatra.glyphs.gridded.array_glyph path (via pyramids.plot re-export) and replace the removed color_scale="linear" loose kwarg with color=ColorScaling.linear(). - ugrid-river-channel: its ds.plot(...) calls route through UgridDataset.plot, which now rejects the loose cbar_* kwargs; swap cbar_label=... for colorbar=ColorBar(label=...) and import ColorBar from pyramids.plot. (ugrid-mesh-exploration keeps cbar_label because that cell calls cleopatra's MeshGlyph.plot directly, which still accepts it natively in 0.30.) --- docs/examples/dataset/dataset_collection.ipynb | 4 ++-- docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/examples/dataset/dataset_collection.ipynb b/docs/examples/dataset/dataset_collection.ipynb index 9216fb886c..2534dd6476 100644 --- a/docs/examples/dataset/dataset_collection.ipynb +++ b/docs/examples/dataset/dataset_collection.ipynb @@ -23,7 +23,7 @@ "outputs": [], "source": [ "# NBVAL_IGNORE_OUTPUT\n", - "from cleopatra.array_glyph import FrameLabel\n", + "from pyramids.plot import ColorScaling, FrameLabel\n", "from IPython.display import HTML\n", "\n", "from pyramids.dataset import DatasetCollection\n", @@ -256,7 +256,7 @@ "cleo = m_dataset.plot(\n", " exclude_value=0,\n", " frame_label=FrameLabel(location=(1, 3)),\n", - " color_scale=\"linear\",\n", + " color=ColorScaling.linear(),\n", " vmin=1,\n", " vmax=100,\n", ")\n", diff --git a/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb b/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb index 218b4986d4..b05c70eec8 100644 --- a/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb +++ b/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb @@ -47,7 +47,9 @@ "\n", "from pyramids.netcdf.ugrid import (\n", " UgridDataset,\n", - ")" + ")", + "\n", + "from pyramids.plot import ColorBar\n" ] }, { @@ -135,7 +137,7 @@ " \"bathymetry\",\n", " cmap=\"ocean_r\",\n", " title=\"River Channel Bathymetry\",\n", - " cbar_label=\"Depth (m)\",\n", + " colorbar=ColorBar(label=\"Depth (m)\"),\n", ")\n", "plt.show()" ] @@ -151,7 +153,7 @@ " \"velocity\",\n", " cmap=\"YlOrRd\",\n", " title=\"Flow Velocity (parabolic cross-section)\",\n", - " cbar_label=\"Velocity (m/s)\",\n", + " colorbar=ColorBar(label=\"Velocity (m/s)\"),\n", ")\n", "plt.show()" ] @@ -311,7 +313,7 @@ " \"bathymetry\",\n", " cmap=\"ocean_r\",\n", " title=\"Meander Bend — Bathymetry\",\n", - " cbar_label=\"Depth (m)\",\n", + " colorbar=ColorBar(label=\"Depth (m)\"),\n", ")\n", "plt.show()" ] @@ -489,7 +491,7 @@ " vmax=1.5,\n", " interval=800,\n", " title=\"Tidal Water Level\",\n", - " cbar_label=\"Water Level (m)\",\n", + " colorbar=ColorBar(label=\"Water Level (m)\"),\n", ")\n", "\n", "from IPython.display import HTML\n", From e246b442510052bd738a3e91f00cb93227de4a23 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Wed, 12 Aug 2026 17:30:19 +0200 Subject: [PATCH 28/30] style(tests): drop trailing blank line to satisfy end-of-file-fixer test_plot_facade.py and test_plot.py ended with an extra blank line, which the pre-commit end-of-file-fixer hook flags. Trim each to a single trailing newline. --- tests/dataset/plot/test_plot_facade.py | 1 - tests/ugrid/test_plot.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tests/dataset/plot/test_plot_facade.py b/tests/dataset/plot/test_plot_facade.py index 333651dfee..f77dfdd27b 100644 --- a/tests/dataset/plot/test_plot_facade.py +++ b/tests/dataset/plot/test_plot_facade.py @@ -276,4 +276,3 @@ def test_empty_rgb_options_dict_is_noop(self, multiband_dataset): w for w in captured if issubclass(w.category, DeprecationWarning) ] assert not deprecations, "Empty rgb_options must not emit DeprecationWarning" - diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index 60ad49cfc3..ea05672a11 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -482,4 +482,3 @@ def test_color_and_data_style_reach_mesh_glyph_plot(self): kw = mock_plot.call_args.kwargs assert kw.get("color") is color, "explicit color must reach MeshGlyph.plot" assert kw.get("data_style") is data_style, "explicit data_style must reach it" - From b8d9b9e1f2d0788589d05130e4ef012275e3da09 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Wed, 12 Aug 2026 17:36:23 +0200 Subject: [PATCH 29/30] style: apply ruff-format across branch-owned plot files Pre-commit ruff-format (and isort on the notebook import cells) reflows a few lines the prior --no-verify commits missed: a ternary collapse in DatasetCollection.plot's animation-axis handling, line rewraps in the plot engine/ugrid tests, and merging the ColorBar import next to the sibling first-party imports in the two migrated example notebooks. No behaviour change. --- .../examples/dataset/dataset_collection.ipynb | 2 +- .../netcdf/ugrid/ugrid-river-channel.ipynb | 5 ++--- src/pyramids/dataset/collection.py | 4 +--- tests/dataset/plot/test_plot_engine.py | 22 ++++++++++++------- tests/ugrid/test_plot.py | 4 +--- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/examples/dataset/dataset_collection.ipynb b/docs/examples/dataset/dataset_collection.ipynb index 2534dd6476..ee7fece855 100644 --- a/docs/examples/dataset/dataset_collection.ipynb +++ b/docs/examples/dataset/dataset_collection.ipynb @@ -23,10 +23,10 @@ "outputs": [], "source": [ "# NBVAL_IGNORE_OUTPUT\n", - "from pyramids.plot import ColorScaling, FrameLabel\n", "from IPython.display import HTML\n", "\n", "from pyramids.dataset import DatasetCollection\n", + "from pyramids.plot import ColorScaling, FrameLabel\n", "\n", "%matplotlib inline" ] diff --git a/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb b/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb index b05c70eec8..e61240dbfd 100644 --- a/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb +++ b/docs/examples/netcdf/ugrid/ugrid-river-channel.ipynb @@ -47,9 +47,8 @@ "\n", "from pyramids.netcdf.ugrid import (\n", " UgridDataset,\n", - ")", - "\n", - "from pyramids.plot import ColorBar\n" + ")\n", + "from pyramids.plot import ColorBar" ] }, { diff --git a/src/pyramids/dataset/collection.py b/src/pyramids/dataset/collection.py index 74744cf444..6fd1e02f67 100644 --- a/src/pyramids/dataset/collection.py +++ b/src/pyramids/dataset/collection.py @@ -2797,9 +2797,7 @@ def plot( list(self.time) if self.time is not None else list(range(self.time_length)) ) axis_values = ( - default_labels - if animation_axis_values is None - else animation_axis_values + default_labels if animation_axis_values is None else animation_axis_values ) if not hasattr(axis_values, "__len__"): axis_values = list(axis_values) # materialise a generator override diff --git a/tests/dataset/plot/test_plot_engine.py b/tests/dataset/plot/test_plot_engine.py index 555168cb4c..3809c3cb56 100644 --- a/tests/dataset/plot/test_plot_engine.py +++ b/tests/dataset/plot/test_plot_engine.py @@ -302,7 +302,9 @@ def test_split_is_driven_by_option_keys(self): assert "add_colorbar" not in plot, ( f"`add_colorbar` must not also reach the render call; plot={plot}" ) - assert "kind" in plot, f"`kind` must be force-routed to the render call; plot={plot}" + assert "kind" in plot, ( + f"`kind` must be force-routed to the render call; plot={plot}" + ) assert "kind" not in ctor, f"`kind` must not stay on the ctor; ctor={ctor}" def test_loose_cbar_kwarg_rejected(self): @@ -518,9 +520,7 @@ def test_returned_glyph_supports_apply_style(self): dataset = Dataset.create_from_array( arr=arr, geo=(0, 0.1, 0, 2, 0, -0.1), epsg=4326 ) - glyph = dataset.plot( - band=0, data_style=DataStyle(style="flow_accumulation") - ) + glyph = dataset.plot(band=0, data_style=DataStyle(style="flow_accumulation")) assert glyph.style == "flow_accumulation" glyph.apply_style("topography") assert glyph.style == "topography", ( @@ -795,7 +795,9 @@ def test_explicit_color_group_reaches_render_call(self): mode="plot", color=explicit, ) - assert plot.get("color") is explicit, "explicit color must reach the render call" + assert plot.get("color") is explicit, ( + "explicit color must reach the render call" + ) def test_explicit_groups_reach_render_call(self): """Explicit ``contour`` / ``cells`` / ``data_style`` reach the render call. @@ -809,8 +811,10 @@ def test_explicit_groups_reach_render_call(self): fake_cls, _ctor, plot, *_ = TestRenderArrayKwargRouting._capture_calls() rng = np.random.default_rng(13) arr = rng.random((4, 4)).astype("float32") - contour, cells, data_style = Contour(levels=4), CellValues(show=True), DataStyle( - style="flow_accumulation" + contour, cells, data_style = ( + Contour(levels=4), + CellValues(show=True), + DataStyle(style="flow_accumulation"), ) with patch("cleopatra.glyphs.gridded.array_glyph.ArrayGlyph", new=fake_cls): render_array( @@ -821,6 +825,8 @@ def test_explicit_groups_reach_render_call(self): cells=cells, data_style=data_style, ) - assert plot.get("contour") is contour, "explicit contour must reach the render call" + assert plot.get("contour") is contour, ( + "explicit contour must reach the render call" + ) assert plot.get("cells") is cells, "explicit cells must reach the render call" assert plot.get("data_style") is data_style, "explicit data_style must reach it" diff --git a/tests/ugrid/test_plot.py b/tests/ugrid/test_plot.py index ea05672a11..2c1b97f621 100644 --- a/tests/ugrid/test_plot.py +++ b/tests/ugrid/test_plot.py @@ -323,9 +323,7 @@ def test_style_and_hillshade_reach_mesh_glyph_plot(self): ) kw = mock_plot.call_args.kwargs data_style = kw.get("data_style") - assert data_style is not None, ( - "the data_style group must reach MeshGlyph.plot" - ) + assert data_style is not None, "the data_style group must reach MeshGlyph.plot" assert data_style.style == "flow_accumulation" assert data_style.hillshade is True From 4530b59713ee7e50f877391508e8d77222c5d128 Mon Sep 17 00:00:00 2001 From: Mostafa Farrag Date: Wed, 12 Aug 2026 17:40:03 +0200 Subject: [PATCH 30/30] test(plot): hoist dataset build out of pytest.raises (S5778) SonarCloud python:S5778 flags the loose-cbar rejection test because two calls inside the pytest.raises block can throw: self._dataset() and .plot(). Build the dataset before the block so only the asserted .plot() call is under raises. --- tests/dataset/plot/test_new_render_params.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/dataset/plot/test_new_render_params.py b/tests/dataset/plot/test_new_render_params.py index a6054c503d..4f5f9b0533 100644 --- a/tests/dataset/plot/test_new_render_params.py +++ b/tests/dataset/plot/test_new_render_params.py @@ -93,5 +93,6 @@ def test_loose_cbar_kwarg_rejected(self): `Dataset.plot` facade raises a `ValueError` naming `pyramids.plot.ColorBar` instead of being folded or forwarded. """ + dataset = self._dataset() with pytest.raises(ValueError, match="ColorBar"): - self._dataset().plot(band=0, cbar_label="mm", cbar_length=0.85) + dataset.plot(band=0, cbar_label="mm", cbar_length=0.85)