diff --git a/.circleci/conda-lock-3.10.yml b/.circleci/conda-lock-3.10.yml index f8a7d8bbb..7c698e7cf 100644 --- a/.circleci/conda-lock-3.10.yml +++ b/.circleci/conda-lock-3.10.yml @@ -9,13 +9,13 @@ # To update a single package to the latest version compatible with the version constraints in the source: # conda-lock lock --lockfile conda-lock-3.10.yml --update PACKAGE # To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f environment-10.yml -f ../../../../../../../../tmp/tmpeqclynjn/environment.yml --lockfile conda-lock-3.10.yml +# conda-lock -f environment-10.yml -f ../../../../../../../../tmp/tmp4vctyyue/environment.yml -f ../../../../../../../../tmp/tmpprqwdrea/environment.yml --lockfile conda-lock-3.10.yml version: 1 metadata: content_hash: - linux-64: efa36e59f86c202671ff971f9881615aaa7c862d3117f7252b84594fb6a229d4 - osx-64: 66c7f614a4322e6d63567a3a69dbeb10855b3456d8ab51fde7f52d14010c3cf6 - osx-arm64: 6e9aeaebe7eb880a089ecefbc49c429784ca51476e55d5cdc20f77e3f82c9993 + linux-64: b4d8aa22cc81f71f8d4b932cd8fe80c23a4cdb0975b6783311f13ee423c56359 + osx-64: efd7431e339a6b346845bd89432f881694e4538f7c0d60df41d5c32a4f0c0813 + osx-arm64: 12ff5c7937b70ae15b66af0b57affa83d8c9e7b8360f27b8fbb34405814b6949 channels: - url: conda-forge used_env_vars: [] @@ -25,7 +25,8 @@ metadata: - osx-arm64 sources: - environment-10.yml - - ../../../../../../../../tmp/tmpeqclynjn/environment.yml + - ../../../../../../../../tmp/tmp4vctyyue/environment.yml + - ../../../../../../../../tmp/tmpprqwdrea/environment.yml package: - name: _libgcc_mutex version: '0.1' @@ -1957,48 +1958,48 @@ package: category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3-stubs @@ -2047,7 +2048,7 @@ package: category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: @@ -2055,14 +2056,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: @@ -2070,14 +2071,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: @@ -2085,10 +2086,10 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore-stubs @@ -3437,7 +3438,7 @@ package: category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: linux-64 dependencies: @@ -3453,14 +3454,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.0-hc85cc9f_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.1-hc85cc9f_0.conda hash: - md5: 63080125641ce03edb003ba6cb3639d0 - sha256: e7f4837d1d74368bcda30aaae545af72fe8a83abd86666e0a56a6fcb744e6508 + md5: c090226f6d82c9bb396948065c3808d9 + sha256: 00f6c0883b8365e141a77d1777339817fe4c582e6cf1e39c69fb0b3eb4349b3a category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-64 dependencies: @@ -3475,14 +3476,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.0-h118fb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.1-h118fb26_0.conda hash: - md5: eeaba0b95ae7d9cf3834686f37f5ff2a - sha256: ceb6b5a8185534b1ca5d93d1ad36ca606afc7515e5b90a99f5d01c438f7e7664 + md5: 07d353575f84d6ccaa01fcdfd2d9db0c + sha256: 72d8ae208757d404a6dd2de94a1f9dd314dbb5f3c0f0ee89424a5444fb9e3386 category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-arm64 dependencies: @@ -3497,10 +3498,10 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.0-hae74ae4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.1-hae74ae4_0.conda hash: - md5: 65d333c04dcdbea01b16993358df3364 - sha256: 0e00e9c1944e594f293e10b4e4abd4505f098d8d63c95c455b80775abcf134fa + md5: 8a19f6de15b62a0ad43fc5959e8bb325 + sha256: ec1d31c48cce2c94bd5ed29bb3af809845a32af255704c2cc87b6106e140b04a category: main optional: false - name: colorama @@ -4750,6 +4751,42 @@ package: sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca category: main optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: linux-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-arm64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false - name: executing version: 2.2.0 manager: conda @@ -5262,7 +5299,7 @@ package: category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: linux-64 dependencies: @@ -5273,14 +5310,14 @@ package: python: '>=3.10,<3.11.0a0' python_abi: 3.10.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.1-py310h3406613_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.2-py310h3406613_0.conda hash: - md5: 14e450afac608165ced4b0b93cfc1df1 - sha256: b634c855e3308e3463d75d57ef8188b023c14778c6ede7fc2ddddd22f7ee2df7 + md5: 32dab042830c3c31f89cdb6273585165 + sha256: afbdc6fd696ce74a94dd558512f532a8e71c653a18f226b1bae9b37e447ae4f0 category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-64 dependencies: @@ -5290,14 +5327,14 @@ package: python: '>=3.10,<3.11.0a0' python_abi: 3.10.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.1-py310h929a2ac_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.2-py310h929a2ac_0.conda hash: - md5: c9a3516335b0019686f115d717aa95a5 - sha256: 0f5b7368f6a4bd8f0e96074647ae68ad07e413014c3133e7d2d6b784c40f2572 + md5: e64981c1a9bf7e3481fba65511b22fdd + sha256: 66829d13d0b03b2390d2d2151066f6d1e6750df6cbbc61e647317b9e0a7cafae category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-arm64 dependencies: @@ -5307,10 +5344,10 @@ package: python: '>=3.10,<3.11.0a0' python_abi: 3.10.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.1-py310h5f69134_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.2-py310h5f69134_0.conda hash: - md5: 383f9d079e589adf155ac30ceea17a75 - sha256: e6dbd3933a073ab7f04c923d45d830530da1275e3afe20de345988ecdaff8cac + md5: 50187739bc03dee9b89542b6fe223cd1 + sha256: 1077770d149c58060352926426c8e3a06f587603959099e1dc230f552dad0eda category: main optional: false - name: fqdn @@ -6543,31 +6580,31 @@ package: category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-arm64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h5netcdf @@ -6665,7 +6702,7 @@ package: category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: linux-64 dependencies: @@ -6680,14 +6717,14 @@ package: libglib: '>=2.84.3,<3.0a0' libstdcxx: '>=14' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.3-h15599e2_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.4-h15599e2_0.conda hash: - md5: e8d443a6375b0b266f0cb89ce22ccaa2 - sha256: 76bd39d9dbb2c982e017313a5c9163bdd2dfd95677fe05d1ea08edbed26de0e6 + md5: a0bddb46e3b740e019b4194e66a9c1fc + sha256: d4818bc75f840db25ba1a0025cfbfe6dd6527eab7a374b25556a09d59d75a7d3 category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-64 dependencies: @@ -6701,14 +6738,14 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.3-h0ffbb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.4-h0ffbb26_0.conda hash: - md5: 5c5f8a58b6a3f50c17bc74011c0879f8 - sha256: 97bea280c0ee8898d0165e95af4752c0de7de572f5bebaeb180d4b57f02999ad + md5: 2bf7ee55fbaa26d73da8923594b172bb + sha256: 1955fa7ca36ea781da859535366c079454ba373f5ec60825a8e7c44485a9bc2b category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-arm64 dependencies: @@ -6722,10 +6759,10 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.3-hf4e55d4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.4-hf4e55d4_0.conda hash: - md5: e577ca64b17624c0382ac19c719f72bc - sha256: e3dd1c98f4f8cb27b3218fad87e1db31d2db0df8f5cc5a23d2f67858e065366a + md5: b7c5258c0c5427e0b115d517a1ed9e2c + sha256: 83ae631d8906677e60c39a6a5ef5e2dad36bffa7b55a62400f7dc79e10ceb7b4 category: main optional: false - name: hdf4 @@ -7976,42 +8013,42 @@ package: category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: json-c @@ -12102,10 +12139,10 @@ package: libgcc: '>=14' openldap: '>=2.6.10,<2.7.0a0' openssl: '>=3.5.2,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_1.conda hash: - md5: de8839c8dde1cba9335ac43d86e16d65 - sha256: 56ba34c2b3ae008a6623a59b14967366b296d884723ace95596cc986d31594a0 + md5: bcee8587faf5dce5050a01817835eaed + sha256: 1b3323f5553db17cad2b0772f6765bf34491e752bfe73077977d376679f97420 category: main optional: false - name: libprotobuf @@ -14271,51 +14308,51 @@ package: category: main optional: false - name: mkdocstrings-python - version: 1.17.0 + version: 1.18.0 manager: conda platform: linux-64 dependencies: - griffe: '>=1.12.1' + griffe: '>=1.13' mkdocs-autorefs: '>=1.4' mkdocstrings: '>=0.30' - python: '>=3.9' + python: '>=3.10' typing_extensions: '>=4.0' - url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.17.0-pyhff2d567_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.18.0-pyh332efcf_0.conda hash: - md5: 62e04ed474c051030432d8d83d3d4d76 - sha256: 74decf2285325b56054be65edd0a9543fe49251310c32710fefb0f96943af5c4 + md5: 702f37a4fe7b009213116a75a7633408 + sha256: cb635753546c1e2639c7e59737548dcd1f2fcf2aadd66cbf235a2d1e429b1dd1 category: main optional: false - name: mkdocstrings-python - version: 1.17.0 + version: 1.18.0 manager: conda platform: osx-64 dependencies: - griffe: '>=1.12.1' + griffe: '>=1.13' mkdocs-autorefs: '>=1.4' mkdocstrings: '>=0.30' - python: '>=3.9' + python: '>=3.10' typing_extensions: '>=4.0' - url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.17.0-pyhff2d567_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.18.0-pyh332efcf_0.conda hash: - md5: 62e04ed474c051030432d8d83d3d4d76 - sha256: 74decf2285325b56054be65edd0a9543fe49251310c32710fefb0f96943af5c4 + md5: 702f37a4fe7b009213116a75a7633408 + sha256: cb635753546c1e2639c7e59737548dcd1f2fcf2aadd66cbf235a2d1e429b1dd1 category: main optional: false - name: mkdocstrings-python - version: 1.17.0 + version: 1.18.0 manager: conda platform: osx-arm64 dependencies: - griffe: '>=1.12.1' + griffe: '>=1.13' mkdocs-autorefs: '>=1.4' mkdocstrings: '>=0.30' - python: '>=3.9' + python: '>=3.10' typing_extensions: '>=4.0' - url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.17.0-pyhff2d567_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.18.0-pyh332efcf_0.conda hash: - md5: 62e04ed474c051030432d8d83d3d4d76 - sha256: 74decf2285325b56054be65edd0a9543fe49251310c32710fefb0f96943af5c4 + md5: 702f37a4fe7b009213116a75a7633408 + sha256: cb635753546c1e2639c7e59737548dcd1f2fcf2aadd66cbf235a2d1e429b1dd1 category: main optional: false - name: msgpack-python @@ -16325,42 +16362,42 @@ package: category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: psutil @@ -17616,6 +17653,48 @@ package: sha256: 25afa7d9387f2aa151b45eb6adf05f9e9e3f58c8de2bc09be7e85c114118eeb9 category: main optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: linux-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-arm64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false - name: python version: 3.10.18 manager: conda @@ -18836,7 +18915,7 @@ package: category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: linux-64 dependencies: @@ -18844,38 +18923,38 @@ package: libgcc: '>=14' python: '' python_abi: 3.10.* - url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.0-py310hd8f68c5_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py310hd8f68c5_0.conda hash: - md5: 40a2626d9988362dfaa3c5e888735bc8 - sha256: b306b7781493ed219a313ac82c8e16860beb077bce193b08aaa30242022a7ce7 + md5: 4eed975c85e20068274d3c7a94072b8a + sha256: a4b7cc6656138c7a89a74f60a086f99abc013876fa57ebfff5f60c416aa8f14c category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-64 dependencies: __osx: '>=10.13' python: '' python_abi: 3.10.* - url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.0-py310h80fed0c_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py310h80fed0c_0.conda hash: - md5: 3c67ec8036df4f795a8c503cf82e2a73 - sha256: 5c76b3cea3a349491a9ee2e1115cf131af88765641c9629f93bbef84a0c31726 + md5: 3f0191aede1dea8fc53fc359369e3dd3 + sha256: 6140da8d84091e4ba1c5fe936f0ebe8518c32dcb8c8b98e245584757fb619403 category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-arm64 dependencies: __osx: '>=11.0' python: 3.10.* python_abi: 3.10.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.0-py310h7018d9b_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py310h7018d9b_0.conda hash: - md5: cb1304da09e1e109cc8d81ca2623e03a - sha256: ce9ab9a58dc898a05b592b43cfff545bf124293c106be6aeeacd700382954f2a + md5: ded8a72abc133e703ba3f359c01b43a1 + sha256: 70d323970e0feefb1e10e0517c6e9c32db982e9b09dd133d5959a4ebfac80d6a category: main optional: false - name: ruamel.yaml @@ -19654,39 +19733,39 @@ package: category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: linux-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: sqlite diff --git a/.circleci/conda-lock-3.11.yml b/.circleci/conda-lock-3.11.yml index d4991fbcb..2da48bc14 100644 --- a/.circleci/conda-lock-3.11.yml +++ b/.circleci/conda-lock-3.11.yml @@ -9,13 +9,13 @@ # To update a single package to the latest version compatible with the version constraints in the source: # conda-lock lock --lockfile conda-lock-3.11.yml --update PACKAGE # To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f environment-11.yml -f ../../../../../../../../tmp/tmpaapka6eu/environment.yml --lockfile conda-lock-3.11.yml +# conda-lock -f environment-11.yml -f ../../../../../../../../tmp/tmpcuv733c3/environment.yml -f ../../../../../../../../tmp/tmpawlz5goq/environment.yml --lockfile conda-lock-3.11.yml version: 1 metadata: content_hash: - linux-64: e83897f1797f99f6aed268ab2260110c28e98aea9826f510776d79c6f659ac1f - osx-64: f144890210f8963cc24ff6feefad270a458497a5c8b398dd8913e407cf67d225 - osx-arm64: 2b25490604413b03d0a6450ca3172b1d8d13678fd2efb437e04e112846343ad3 + linux-64: 7bf02c7355297460580377c155fafc27b6ba523d5d84396ee0aac9a5d1f81b96 + osx-64: 5d3ff53ffb16ee72ff0188839c8edfe051af035164a2c7d00b2798c748d400fe + osx-arm64: bdc9e099200387a685c498b6eae1990ca756f3c6775e7646319c10846b5b7131 channels: - url: conda-forge used_env_vars: [] @@ -25,7 +25,8 @@ metadata: - osx-arm64 sources: - environment-11.yml - - ../../../../../../../../tmp/tmpaapka6eu/environment.yml + - ../../../../../../../../tmp/tmpcuv733c3/environment.yml + - ../../../../../../../../tmp/tmpawlz5goq/environment.yml package: - name: _libgcc_mutex version: '0.1' @@ -1957,48 +1958,48 @@ package: category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3-stubs @@ -2047,7 +2048,7 @@ package: category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: @@ -2055,14 +2056,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: @@ -2070,14 +2071,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: @@ -2085,10 +2086,10 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore-stubs @@ -3437,7 +3438,7 @@ package: category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: linux-64 dependencies: @@ -3453,14 +3454,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.0-hc85cc9f_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.1-hc85cc9f_0.conda hash: - md5: 63080125641ce03edb003ba6cb3639d0 - sha256: e7f4837d1d74368bcda30aaae545af72fe8a83abd86666e0a56a6fcb744e6508 + md5: c090226f6d82c9bb396948065c3808d9 + sha256: 00f6c0883b8365e141a77d1777339817fe4c582e6cf1e39c69fb0b3eb4349b3a category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-64 dependencies: @@ -3475,14 +3476,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.0-h118fb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.1-h118fb26_0.conda hash: - md5: eeaba0b95ae7d9cf3834686f37f5ff2a - sha256: ceb6b5a8185534b1ca5d93d1ad36ca606afc7515e5b90a99f5d01c438f7e7664 + md5: 07d353575f84d6ccaa01fcdfd2d9db0c + sha256: 72d8ae208757d404a6dd2de94a1f9dd314dbb5f3c0f0ee89424a5444fb9e3386 category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-arm64 dependencies: @@ -3497,10 +3498,10 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.0-hae74ae4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.1-hae74ae4_0.conda hash: - md5: 65d333c04dcdbea01b16993358df3364 - sha256: 0e00e9c1944e594f293e10b4e4abd4505f098d8d63c95c455b80775abcf134fa + md5: 8a19f6de15b62a0ad43fc5959e8bb325 + sha256: ec1d31c48cce2c94bd5ed29bb3af809845a32af255704c2cc87b6106e140b04a category: main optional: false - name: colorama @@ -4786,6 +4787,42 @@ package: sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca category: main optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: linux-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-arm64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false - name: executing version: 2.2.0 manager: conda @@ -5298,7 +5335,7 @@ package: category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: linux-64 dependencies: @@ -5309,14 +5346,14 @@ package: python: '>=3.11,<3.12.0a0' python_abi: 3.11.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.1-py311h3778330_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.2-py311h3778330_0.conda hash: - md5: a879d36924dd853bf855ed423b02d92b - sha256: a272826eb8bda4c7207db735448f67f1e5ce79a08eb5a78271c62d9ea452a275 + md5: 5be2463c4d16a021dd571d7bf56ac799 + sha256: f2685b212f3d84d2ba4fc89a03442724a94166ee8a9c1719efed0d7a07d474cb category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-64 dependencies: @@ -5326,14 +5363,14 @@ package: python: '>=3.11,<3.12.0a0' python_abi: 3.11.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.1-py311hfbe4617_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.2-py311hfbe4617_0.conda hash: - md5: ec5452a3bdfb6f0859112e4718e4aa8b - sha256: 85e1e43313863600b8058bfd3df4beb5af9ec286e8516d1eefc692f35189b033 + md5: d5648fdf64b8aa74dddd992740d6ccc5 + sha256: 84dddca09970ecd2fe32f557faa1b9706bfeea6433e415e797c3313c9a5300a9 category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-arm64 dependencies: @@ -5343,10 +5380,10 @@ package: python: '>=3.11,<3.12.0a0' python_abi: 3.11.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.1-py311h2fe624c_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.2-py311h2fe624c_0.conda hash: - md5: 63fca626eb5dab4df417ebb705d2925d - sha256: 33cc2adcd9ac384a9cad0b5a48dcb34bd87462c56bb8e43cb20cc82ac9ba8225 + md5: 70bc71c4717c3a4700988f96eeabf09a + sha256: 2d3dadff13a846513f81ece2ae356e4502f4edb5a5ebe58d21ecac7befed072a category: main optional: false - name: fqdn @@ -6565,45 +6602,45 @@ package: category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: linux-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-arm64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h5netcdf @@ -6701,7 +6738,7 @@ package: category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: linux-64 dependencies: @@ -6716,14 +6753,14 @@ package: libglib: '>=2.84.3,<3.0a0' libstdcxx: '>=14' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.3-h15599e2_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.4-h15599e2_0.conda hash: - md5: e8d443a6375b0b266f0cb89ce22ccaa2 - sha256: 76bd39d9dbb2c982e017313a5c9163bdd2dfd95677fe05d1ea08edbed26de0e6 + md5: a0bddb46e3b740e019b4194e66a9c1fc + sha256: d4818bc75f840db25ba1a0025cfbfe6dd6527eab7a374b25556a09d59d75a7d3 category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-64 dependencies: @@ -6737,14 +6774,14 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.3-h0ffbb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.4-h0ffbb26_0.conda hash: - md5: 5c5f8a58b6a3f50c17bc74011c0879f8 - sha256: 97bea280c0ee8898d0165e95af4752c0de7de572f5bebaeb180d4b57f02999ad + md5: 2bf7ee55fbaa26d73da8923594b172bb + sha256: 1955fa7ca36ea781da859535366c079454ba373f5ec60825a8e7c44485a9bc2b category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-arm64 dependencies: @@ -6758,10 +6795,10 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.3-hf4e55d4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.4-hf4e55d4_0.conda hash: - md5: e577ca64b17624c0382ac19c719f72bc - sha256: e3dd1c98f4f8cb27b3218fad87e1db31d2db0df8f5cc5a23d2f67858e065366a + md5: b7c5258c0c5427e0b115d517a1ed9e2c + sha256: 83ae631d8906677e60c39a6a5ef5e2dad36bffa7b55a62400f7dc79e10ceb7b4 category: main optional: false - name: hdf4 @@ -8054,42 +8091,42 @@ package: category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: json-c @@ -12180,10 +12217,10 @@ package: libgcc: '>=14' openldap: '>=2.6.10,<2.7.0a0' openssl: '>=3.5.2,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_1.conda hash: - md5: de8839c8dde1cba9335ac43d86e16d65 - sha256: 56ba34c2b3ae008a6623a59b14967366b296d884723ace95596cc986d31594a0 + md5: bcee8587faf5dce5050a01817835eaed + sha256: 1b3323f5553db17cad2b0772f6765bf34491e752bfe73077977d376679f97420 category: main optional: false - name: libprotobuf @@ -14349,19 +14386,19 @@ package: category: main optional: false - name: mkdocstrings-python - version: 1.17.0 + version: 1.18.0 manager: conda platform: linux-64 dependencies: - griffe: '>=1.12.1' + griffe: '>=1.13' mkdocs-autorefs: '>=1.4' mkdocstrings: '>=0.30' - python: '>=3.9' + python: '>=3.10' typing_extensions: '>=4.0' - url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.17.0-pyhff2d567_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/mkdocstrings-python-1.18.0-pyh332efcf_0.conda hash: - md5: 62e04ed474c051030432d8d83d3d4d76 - sha256: 74decf2285325b56054be65edd0a9543fe49251310c32710fefb0f96943af5c4 + md5: 702f37a4fe7b009213116a75a7633408 + sha256: cb635753546c1e2639c7e59737548dcd1f2fcf2aadd66cbf235a2d1e429b1dd1 category: main optional: false - name: mkdocstrings-python @@ -15202,12 +15239,12 @@ package: libgcc: '>=14' liblapack: '>=3.9.0,<4.0a0' libstdcxx: '>=14' - python: '>=3.11,<3.12.0a0' + python: '' python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py311h9517ec2_1.conda + url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py311h2e04523_2.conda hash: - md5: 768227d126f180ca9212d2ce8d69497d - sha256: 079a9183d7e15afb3ec75290e1c3618959241fa6f2bd67fd5a659281560eff12 + md5: 7fb6248106c55e3ccd29ac675be01fac + sha256: a8be67f8be3354e33da3adacb021f7f93971ddc466d79eab412151bf5dc58cf4 category: main optional: false - name: numpy @@ -15220,12 +15257,12 @@ package: libcblas: '>=3.9.0,<4.0a0' libcxx: '>=19' liblapack: '>=3.9.0,<4.0a0' - python: '>=3.11,<3.12.0a0' + python: '' python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py311h594a81e_1.conda + url: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py311h09fcace_2.conda hash: - md5: cd946a2d8a3c0545be2be1927abbddce - sha256: 7828af4d601c88a90d88c0e55dee20b9aadcea6966d43c053e72fd4c8b0067d1 + md5: 3fd67cc81b4297d403e3bfde14ab409e + sha256: 7db239350fcce5f16bcb2891578be74a17cea9aa61506b427d3d0fc528b0a18f category: main optional: false - name: numpy @@ -15238,12 +15275,12 @@ package: libcblas: '>=3.9.0,<4.0a0' libcxx: '>=19' liblapack: '>=3.9.0,<4.0a0' - python: '>=3.11,<3.12.0a0' + python: 3.11.* python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py311h53913e0_1.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py311h0856f98_2.conda hash: - md5: 0b5fea02599032d5bfe1bbf8790ff060 - sha256: e5ce2c3714476ad39b0cd652b3803470738a584c2572c00b03cccbdad83b520c + md5: c55c144bf8ee31580fe4fecaab9043a5 + sha256: b2cb7fc23d462840fec513fbb5ca4467a2d8b3e9ab2588096443b80d9f286058 category: main optional: false - name: openjpeg @@ -16484,42 +16521,42 @@ package: category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: psutil @@ -17775,6 +17812,48 @@ package: sha256: 25afa7d9387f2aa151b45eb6adf05f9e9e3f58c8de2bc09be7e85c114118eeb9 category: main optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: linux-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-arm64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false - name: python version: 3.11.13 manager: conda @@ -18995,7 +19074,7 @@ package: category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: linux-64 dependencies: @@ -19003,38 +19082,38 @@ package: libgcc: '>=14' python: '' python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.0-py311h902ca64_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py311h902ca64_0.conda hash: - md5: 397e7e07356db9425069fa86e8920404 - sha256: c32892bc6ec30f932424c6a02f2b6de1062581a1cc942127792ec7980ddc31aa + md5: 486cb477243a9538130c439e66277069 + sha256: 72231a2721aa5d1bcee36bf1a33e57e500f820feb09058700365ea8e5d9c982d category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-64 dependencies: __osx: '>=10.13' python: '' python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.0-py311hd3d88a1_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py311hd3d88a1_0.conda hash: - md5: b133a892de6369b6971ac2a83ae396c8 - sha256: fe6a950458dcded2c4e7a879eba06f5b3b699fe6a5fcc2ee2882785d459f2874 + md5: 2313cb4dcdec1663a07c03d3181111ea + sha256: e4b010b8576d2da3826f0d31e298bf4af6dd22cc3c4b99ac537aa378bbae56f9 category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-arm64 dependencies: __osx: '>=11.0' python: 3.11.* python_abi: 3.11.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.0-py311h1c3fc1a_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py311h1c3fc1a_0.conda hash: - md5: 65b920af8c94ae5d14cc76ba2c9e07c4 - sha256: a048d46fcfc3974fcbb8dcf3667fc6c904fa7abc343abd9d1de75c05b96b6735 + md5: 56ebedc5c64f5c3d513a9532d67952fc + sha256: 68196572450da817fb3c526ad2737ea793aa9c87f1b4814a1e731c457459c0ea category: main optional: false - name: ruamel.yaml @@ -19813,39 +19892,39 @@ package: category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: linux-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: sqlite diff --git a/.circleci/conda-lock-3.12.yml b/.circleci/conda-lock-3.12.yml index 6b4e78fa4..252cfc413 100644 --- a/.circleci/conda-lock-3.12.yml +++ b/.circleci/conda-lock-3.12.yml @@ -9,13 +9,13 @@ # To update a single package to the latest version compatible with the version constraints in the source: # conda-lock lock --lockfile conda-lock-3.12.yml --update PACKAGE # To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f environment.yml -f ../../../../../../../../tmp/tmper1hbhr4/environment.yml --lockfile conda-lock-3.12.yml +# conda-lock -f environment.yml -f ../../../../../../../../tmp/tmper1hbhr4/environment.yml -f ../../../../../../../../tmp/tmpqx05dac7/environment.yml --lockfile conda-lock-3.12.yml version: 1 metadata: content_hash: - linux-64: e1bdcc21bbc4473294aa1467abfef692db32b7e3dfac10502db7f5b7b62b768f - osx-64: 941e72be2d8e0d7d2708d324f7706c37401a1a78dd4cdb413a7882150a336f1d - osx-arm64: 5254e8381e96a9be4d78ae88c17d6e2a8245d1b462e2494ffd7bf9ce36f3424c + linux-64: 24faf8990d56d9d66faef74cc63ad172d704e22569facaee502723b4faf5918d + osx-64: 06365f25f85452e80422d1b54d3e6af05b590742f61d5002534906dad8ff2ff2 + osx-arm64: 726ff837b6a16e883d0227a2a645b463f63a6036c655d44ffcd279757c89b701 channels: - url: conda-forge used_env_vars: [] @@ -26,6 +26,7 @@ metadata: sources: - environment.yml - ../../../../../../../../tmp/tmper1hbhr4/environment.yml + - ../../../../../../../../tmp/tmpqx05dac7/environment.yml package: - name: _libgcc_mutex version: '0.1' @@ -1996,48 +1997,48 @@ package: category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3-stubs @@ -2086,7 +2087,7 @@ package: category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: @@ -2094,14 +2095,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: @@ -2109,14 +2110,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: @@ -2124,10 +2125,10 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore-stubs @@ -3476,7 +3477,7 @@ package: category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: linux-64 dependencies: @@ -3492,14 +3493,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.0-hc85cc9f_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.1-hc85cc9f_0.conda hash: - md5: 63080125641ce03edb003ba6cb3639d0 - sha256: e7f4837d1d74368bcda30aaae545af72fe8a83abd86666e0a56a6fcb744e6508 + md5: c090226f6d82c9bb396948065c3808d9 + sha256: 00f6c0883b8365e141a77d1777339817fe4c582e6cf1e39c69fb0b3eb4349b3a category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-64 dependencies: @@ -3514,14 +3515,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.0-h118fb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.1-h118fb26_0.conda hash: - md5: eeaba0b95ae7d9cf3834686f37f5ff2a - sha256: ceb6b5a8185534b1ca5d93d1ad36ca606afc7515e5b90a99f5d01c438f7e7664 + md5: 07d353575f84d6ccaa01fcdfd2d9db0c + sha256: 72d8ae208757d404a6dd2de94a1f9dd314dbb5f3c0f0ee89424a5444fb9e3386 category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-arm64 dependencies: @@ -3536,10 +3537,10 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.0-hae74ae4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.1-hae74ae4_0.conda hash: - md5: 65d333c04dcdbea01b16993358df3364 - sha256: 0e00e9c1944e594f293e10b4e4abd4505f098d8d63c95c455b80775abcf134fa + md5: 8a19f6de15b62a0ad43fc5959e8bb325 + sha256: ec1d31c48cce2c94bd5ed29bb3af809845a32af255704c2cc87b6106e140b04a category: main optional: false - name: colorama @@ -4850,6 +4851,42 @@ package: sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca category: main optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: linux-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-arm64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false - name: executing version: 2.2.0 manager: conda @@ -5362,7 +5399,7 @@ package: category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: linux-64 dependencies: @@ -5373,14 +5410,14 @@ package: python: '>=3.12,<3.13.0a0' python_abi: 3.12.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.1-py312h8a5da7c_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.2-py312h8a5da7c_0.conda hash: - md5: 313520338e97b747315b5be6a563c315 - sha256: 8c65a6c9592828ca767161b47e66e66fe8d32b8e1f8af37b10b6594ad1c77340 + md5: 4c3f3c752ec0cd37b0a0990af20fd952 + sha256: da1c642961e2cad6748266c55ee625062fbdec9f191dc16a29859b2b996a4eea category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-64 dependencies: @@ -5390,14 +5427,14 @@ package: python: '>=3.12,<3.13.0a0' python_abi: 3.12.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.1-py312h3d55d04_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.2-py312h3d55d04_0.conda hash: - md5: 035064981f3aed435cffb63cbd11e5ef - sha256: e37ef154575bd508aa2d5f7cd4f998aa6f03aa13cd9a31de18b67ad0db270ba6 + md5: d634f090d804ab3d35abf2c7c02b4053 + sha256: d9d8764c6693d65f39d5726afc1eff054ea5172ededd0ea41681774ad98f43bf category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-arm64 dependencies: @@ -5407,10 +5444,10 @@ package: python: '>=3.12,<3.13.0a0' python_abi: 3.12.* unicodedata2: '>=15.1.0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.1-py312h6daa0e5_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.2-py312h6daa0e5_0.conda hash: - md5: 55d9d37b29f97b6cd08d6c3dcc8a0712 - sha256: 2751b170e19e03252b4e3a537f42e62396d7a87afa5b8ebce97eea565abbb95a + md5: 715f0ac9bac5abe18951f66738cc3e3a + sha256: d566e5096981a70c87523ff7aff057c9c13bab851df861861003482efb7825e4 category: main optional: false - name: fqdn @@ -6629,45 +6666,45 @@ package: category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: linux-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-arm64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h5netcdf @@ -6765,7 +6802,7 @@ package: category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: linux-64 dependencies: @@ -6780,14 +6817,14 @@ package: libglib: '>=2.84.3,<3.0a0' libstdcxx: '>=14' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.3-h15599e2_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.4-h15599e2_0.conda hash: - md5: e8d443a6375b0b266f0cb89ce22ccaa2 - sha256: 76bd39d9dbb2c982e017313a5c9163bdd2dfd95677fe05d1ea08edbed26de0e6 + md5: a0bddb46e3b740e019b4194e66a9c1fc + sha256: d4818bc75f840db25ba1a0025cfbfe6dd6527eab7a374b25556a09d59d75a7d3 category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-64 dependencies: @@ -6801,14 +6838,14 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.3-h0ffbb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.4-h0ffbb26_0.conda hash: - md5: 5c5f8a58b6a3f50c17bc74011c0879f8 - sha256: 97bea280c0ee8898d0165e95af4752c0de7de572f5bebaeb180d4b57f02999ad + md5: 2bf7ee55fbaa26d73da8923594b172bb + sha256: 1955fa7ca36ea781da859535366c079454ba373f5ec60825a8e7c44485a9bc2b category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-arm64 dependencies: @@ -6822,10 +6859,10 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.3-hf4e55d4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.4-hf4e55d4_0.conda hash: - md5: e577ca64b17624c0382ac19c719f72bc - sha256: e3dd1c98f4f8cb27b3218fad87e1db31d2db0df8f5cc5a23d2f67858e065366a + md5: b7c5258c0c5427e0b115d517a1ed9e2c + sha256: 83ae631d8906677e60c39a6a5ef5e2dad36bffa7b55a62400f7dc79e10ceb7b4 category: main optional: false - name: hdf4 @@ -8113,42 +8150,42 @@ package: category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: json-c @@ -12239,10 +12276,10 @@ package: libgcc: '>=14' openldap: '>=2.6.10,<2.7.0a0' openssl: '>=3.5.2,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_1.conda hash: - md5: de8839c8dde1cba9335ac43d86e16d65 - sha256: 56ba34c2b3ae008a6623a59b14967366b296d884723ace95596cc986d31594a0 + md5: bcee8587faf5dce5050a01817835eaed + sha256: 1b3323f5553db17cad2b0772f6765bf34491e752bfe73077977d376679f97420 category: main optional: false - name: libprotobuf @@ -15261,12 +15298,12 @@ package: libgcc: '>=14' liblapack: '>=3.9.0,<4.0a0' libstdcxx: '>=14' - python: '>=3.12,<3.13.0a0' + python: '' python_abi: 3.12.* - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py312hf476fde_1.conda + url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py312h33ff503_2.conda hash: - md5: c935705f0365a066b2ef76eb1c431fe9 - sha256: c910f0b010adb642cb1542aeb9ade5c618d7a6983c2c3be1ce560bbcbab619cd + md5: b6daf7dbe075ac2ae2ad59cdc45aa8c4 + sha256: 0348fcaaefba8b77e7f9889a0d8ed356ff8aa53c1b69b47697b5c8c5f7d33b0e category: main optional: false - name: numpy @@ -15279,12 +15316,12 @@ package: libcblas: '>=3.9.0,<4.0a0' libcxx: '>=19' liblapack: '>=3.9.0,<4.0a0' - python: '>=3.12,<3.13.0a0' + python: '' python_abi: 3.12.* - url: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py312h3bb93b5_1.conda + url: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py312hda18a35_2.conda hash: - md5: 5607024afc721a82cdcc2977415bf912 - sha256: c8369b1ccbde32deac597b45e3c77b88a5f3ba3f3340e99be45b0d45c93d5bb2 + md5: 95fac8d8fcce82d91e07aba451c7cdaa + sha256: a6540036fc49d2a5f044adc567b148c2c22bdef34a6a4957727083bb4127995e category: main optional: false - name: numpy @@ -15297,12 +15334,12 @@ package: libcblas: '>=3.9.0,<4.0a0' libcxx: '>=19' liblapack: '>=3.9.0,<4.0a0' - python: '>=3.12,<3.13.0a0' + python: 3.12.* python_abi: 3.12.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py312ha225924_1.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py312h2f38b44_2.conda hash: - md5: f5484030b2e15aabf74f1ec641a5e6d2 - sha256: 608053802b1fc85f4b003a2b8ced1bf502326cff4a0aa4473b083bbd742923ef + md5: ac380ace5a774475d2a75a150d67ded0 + sha256: b1d3dff32c97411778dd67a9920fba810e460f58e1625a8711f7c2eb6d540313 category: main optional: false - name: openjpeg @@ -16543,42 +16580,42 @@ package: category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: psutil @@ -17834,6 +17871,48 @@ package: sha256: 25afa7d9387f2aa151b45eb6adf05f9e9e3f58c8de2bc09be7e85c114118eeb9 category: main optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: linux-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-arm64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false - name: python version: 3.12.11 manager: conda @@ -19096,7 +19175,7 @@ package: category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: linux-64 dependencies: @@ -19104,38 +19183,38 @@ package: libgcc: '>=14' python: '' python_abi: 3.12.* - url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.0-py312h868fb18_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py312h868fb18_0.conda hash: - md5: 3d3d11430ec826a845a0e9d6ccefa294 - sha256: cfc9c79f0e2658754b02efb890fe3c835d865ed0535155787815ae16e56dbe9c + md5: 2c5c390ddeffeb6eecc79df2416783d0 + sha256: d9ace02196f551cbe608fd9d64628239a2101815a96a8a095e7a426b19956176 category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-64 dependencies: __osx: '>=10.13' python: '' python_abi: 3.12.* - url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.0-py312h00ff6fd_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py312h00ff6fd_0.conda hash: - md5: 520e0ccc082eea6649ff7acf18852e51 - sha256: 79698e8fa42df6c28e1082dbafdf9ccb48e68bfc69b324b65d846af88c6254c9 + md5: 552f8c0cb3f903bdbf4c9ca247ec17da + sha256: 2306a04385d8e25d0e7279baed4b65633b4cb4548bdccbbbbb0dd3f78f1808b3 category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-arm64 dependencies: __osx: '>=11.0' python: 3.12.* python_abi: 3.12.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.0-py312h6f58b40_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py312h6f58b40_0.conda hash: - md5: ccbe846733e149a842df80f53f66ca72 - sha256: 0a14b856d41b4ef51a4c67fd8200b18c1c21ba0f252a2e3f9f85678149e08141 + md5: c19f1dc374945c973e6eb9fe8a59eee5 + sha256: 6f5facd8070e49d765e048d1c1020695e92f3599613a8aba906e8b7a138e82cf category: main optional: false - name: ruamel.yaml @@ -19914,39 +19993,39 @@ package: category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: linux-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: sqlite diff --git a/.circleci/conda-lock-3.9.yml b/.circleci/conda-lock-3.9.yml index fe212185f..5492438ba 100644 --- a/.circleci/conda-lock-3.9.yml +++ b/.circleci/conda-lock-3.9.yml @@ -9,13 +9,13 @@ # To update a single package to the latest version compatible with the version constraints in the source: # conda-lock lock --lockfile conda-lock-3.9.yml --update PACKAGE # To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f environment-9.yml -f ../../../../../../../../tmp/tmpv7j5fuec/environment.yml --lockfile conda-lock-3.9.yml +# conda-lock -f environment-9.yml -f ../../../../../../../../tmp/tmp89nfu46r/environment.yml -f ../../../../../../../../tmp/tmpnfyamusf/environment.yml --lockfile conda-lock-3.9.yml version: 1 metadata: content_hash: - linux-64: e223acab8a9275c2958f598daf5343e4f78ebce019288844ce9b7161940b8e32 - osx-64: b056e7669c41b80c573a06cb18c838bb07e54fe3473c3bd73e3ad3569ed9e73b - osx-arm64: c6065c53cb182253dd85ac36bff55637c5f62d68a30a791a1f56e2a9d93daac3 + linux-64: a7103f80ed49c526b31d6a31b83acc8fa8a9c8411abca4d7b5c4fa87deb17a2d + osx-64: 0cc6fc48f64795bf1f76eb2b101a03b70442508438f70bab4d8013e06154b2a3 + osx-arm64: 238a506d859b616a8bc9ad4395734ab61af25ccbf6fa5d15713cda1693ce6e1a channels: - url: conda-forge used_env_vars: [] @@ -25,7 +25,8 @@ metadata: - osx-arm64 sources: - environment-9.yml - - ../../../../../../../../tmp/tmpv7j5fuec/environment.yml + - ../../../../../../../../tmp/tmp89nfu46r/environment.yml + - ../../../../../../../../tmp/tmpnfyamusf/environment.yml package: - name: _libgcc_mutex version: '0.1' @@ -3389,7 +3390,7 @@ package: category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: linux-64 dependencies: @@ -3405,14 +3406,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.0-hc85cc9f_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.1-hc85cc9f_0.conda hash: - md5: 63080125641ce03edb003ba6cb3639d0 - sha256: e7f4837d1d74368bcda30aaae545af72fe8a83abd86666e0a56a6fcb744e6508 + md5: c090226f6d82c9bb396948065c3808d9 + sha256: 00f6c0883b8365e141a77d1777339817fe4c582e6cf1e39c69fb0b3eb4349b3a category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-64 dependencies: @@ -3427,14 +3428,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.0-h118fb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.1-h118fb26_0.conda hash: - md5: eeaba0b95ae7d9cf3834686f37f5ff2a - sha256: ceb6b5a8185534b1ca5d93d1ad36ca606afc7515e5b90a99f5d01c438f7e7664 + md5: 07d353575f84d6ccaa01fcdfd2d9db0c + sha256: 72d8ae208757d404a6dd2de94a1f9dd314dbb5f3c0f0ee89424a5444fb9e3386 category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-arm64 dependencies: @@ -3449,10 +3450,10 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.0-hae74ae4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.1-hae74ae4_0.conda hash: - md5: 65d333c04dcdbea01b16993358df3364 - sha256: 0e00e9c1944e594f293e10b4e4abd4505f098d8d63c95c455b80775abcf134fa + md5: 8a19f6de15b62a0ad43fc5959e8bb325 + sha256: ec1d31c48cce2c94bd5ed29bb3af809845a32af255704c2cc87b6106e140b04a category: main optional: false - name: colorama @@ -4750,6 +4751,42 @@ package: sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca category: main optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: linux-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-arm64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false - name: executing version: 2.2.0 manager: conda @@ -12196,10 +12233,10 @@ package: libgcc: '>=14' openldap: '>=2.6.10,<2.7.0a0' openssl: '>=3.5.2,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_1.conda hash: - md5: de8839c8dde1cba9335ac43d86e16d65 - sha256: 56ba34c2b3ae008a6623a59b14967366b296d884723ace95596cc986d31594a0 + md5: bcee8587faf5dce5050a01817835eaed + sha256: 1b3323f5553db17cad2b0772f6765bf34491e752bfe73077977d376679f97420 category: main optional: false - name: libprotobuf @@ -17710,6 +17747,48 @@ package: sha256: 25afa7d9387f2aa151b45eb6adf05f9e9e3f58c8de2bc09be7e85c114118eeb9 category: main optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: linux-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-arm64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false - name: python version: 3.9.23 manager: conda diff --git a/.circleci/config.yml b/.circleci/config.yml index 056725654..3a79c9c91 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,6 +8,8 @@ jobs: docker: - image: cimg/base:current user: root + resource_class: medium + steps: - checkout @@ -73,8 +75,8 @@ jobs: shell: /bin/bash -xl command: | python -m pip install pytest-cov - COV_OPTIONS=`python -c "import importlib.util; print(*(' --cov='+p for p in importlib.util.find_spec('RAiDER').submodule_search_locations))"` - pytest -m "not long" test/ ${COV_OPTIONS} --cov-report= + COV_OPTIONS=`python -c "import importlib.util; print(*(' --cov=' + p for p in importlib.util.find_spec('RAiDER').submodule_search_locations))"` + pytest test/ -n 2 -m "not long" ${COV_OPTIONS} --cov-report= - run: name: Report coverage diff --git a/.circleci/regenerate-locks.py b/.circleci/regenerate-locks.py index da405b2b4..6f51c5de6 100644 --- a/.circleci/regenerate-locks.py +++ b/.circleci/regenerate-locks.py @@ -16,7 +16,7 @@ # Matches the "dependencies" entry for python. # First group: like " - python" -# Second group: like ">=3.8" +# Second group: like ">=3.9" PATTERN_PYTHON_DEP = re.compile(r'^(\s*-\s*python)([<>=~]?=?.+)$', re.MULTILINE) @@ -30,8 +30,8 @@ def generate_lock(out_path: Path, version: str, template: str) -> None: env_path = Path(tmp_dir_str) / 'environment.yml' # Hardcode a copy of the environment.yml file to this Python version - with env_path.open('w', encoding='utf-8') as f_env: - f_env.write(re.sub(PATTERN_PYTHON_DEP, f'\\1={version}', template)) + with env_path.open('w', encoding='utf-8') as f_tmp_env: + f_tmp_env.write(re.sub(PATTERN_PYTHON_DEP, f'\\1={version}', template)) # Platforms explicitly listed in order to exclude win-64, since isce3 # and wand (and therefore RAiDER) are not compatible with Windows. @@ -50,9 +50,11 @@ def main() -> None: # Read RAiDER's supported Python versions from CircleCI config. # The last entry in the list will be placed in the project root. with Path('.circleci/config.yml').open(encoding='utf-8') as f_ci_config: + # fmt: off versions: list[str] = yaml.safe_load(f_ci_config) \ ['workflows']['all-tests']['jobs'][0] \ ['build']['matrix']['parameters']['python-version'] + # fmt: on for i, version in tqdm(enumerate(versions), total=len(versions), unit='lockfiles written'): if i < len(versions) - 1: diff --git a/.gitignore b/.gitignore index b9390d473..9c2694b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,25 +13,11 @@ build CMake* Makefile dummy* -test/data *.log -*.pdf *.cpp -test/*/geom/*.dem -test/*/geom/*.nc -test/test_geom/*.nc -test/scenario_2/GMAO_Delay_*.csv -test/GUNW/ .eggs/ dist/ tools/RAiDER.egg-info/ tools/bindings/utils/makePoints.c -gunw_azimuth_test_data/ -test/synthetic_test/*.nc -test/synthetic_test/weather_files_synth/ -orbits/ .lsp/ -/*.nc -/GUNW*.yaml -/*.tif .coverage* diff --git a/CHANGELOG.md b/CHANGELOG.md index 888db5dde..5eead7c1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,13 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * [753](https://github.com/dbekaert/RAiDER/pull/753) - Removed support for ERA-Interim. ### Changed +* [745](https://github.com/dbekaert/RAiDER/pull/745) - Refactored the test suite to be able to run tests in parallel. * [743](https://github.com/dbekaert/RAiDER/pull/743) - Switched from HTTPS to DAP4 for retrieving MERRA2 data, and suppressed a warning for using DAP4 for GMAO data where doing so is not possible. +* [726](https://github.com/dbekaert/RAiDER/pull/726) - Updated code and documentation for changes to the CDS API. ### Fixed * [765](https://github.com/dbekaert/RAiDER/pull/765) - Fixed a dead link to "Installing from Source" in the ReadMe. +* [761](https://github.com/dbekaert/RAiDER/pull/761) - Misc. fixes for tests `test_GUNW_hyp3_metadata_update...` and `test_cube...`. * [759](https://github.com/dbekaert/RAiDER/pull/759) - Added a `browse` S3 tag for `.png` files when uploaded to AWS. * [751](https://github.com/dbekaert/RAiDER/pull/751) - Fixed ERA-5 interface for a change to its API that requires pressure variables to be requested separately from temperature and humidity. * [741](https://github.com/dbekaert/RAiDER/pull/741) - Updated mamba setup commands in CircleCI for mamba 2.0.0. @@ -32,7 +35,6 @@ and uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * [746](https://github.com/dbekaert/RAiDER/pull/746) - Added support for numpy v2. * [725](https://github.com/dbekaert/RAiDER/pull/725) - Added rules to ignore all test artifacts in git. * [728](https://github.com/dbekaert/RAiDER/pull/728) - Added downloader tests for HRES, GMAO, and MERRA2. -* [726](https://github.com/dbekaert/RAiDER/pull/726) - Updated code and documentation for changes to the CDS API. ## [0.5.5] ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4c65a5d8b..d8b5488c9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -153,5 +153,24 @@ git add conda-lock.yml git add .circleci/conda-lock*.yml ``` +### TIP: Speed up the test suite ### +You can increase the speed that the test suite completes by allocating more than +one core to pytest. By default pytest uses 1 core, but RAiDER comes with +`pytest-xdist` to allow you to distribute the tests across multiple cores and +iterate faster. + +Add this to your pytest arguments: +``` +-n <# cores> +``` +Or, in your vscode workspace settings, you can add: +```json + "python.testing.pytestArgs": [ + "test", + "-n", "logical" + ], +``` + + ### Things you should NOT do (For anyone with push rights to RAiDER or RAiDER-docs) Never modify a commit or the history of anything that has been committed to https://github.com/dbekaert/RAiDER and https://github.com/dbekaert/RAiDER-docs. diff --git a/conda-lock.yml b/conda-lock.yml index e9b3fcd79..7f044b0f3 100644 --- a/conda-lock.yml +++ b/conda-lock.yml @@ -9,13 +9,13 @@ # To update a single package to the latest version compatible with the version constraints in the source: # conda-lock lock --lockfile conda-lock.yml --update PACKAGE # To re-solve the entire environment, e.g. after changing a version constraint in the source file: -# conda-lock -f environment-13.yml -f ../../../../../../../tmp/tmp3d9y6g3q/environment.yml --lockfile conda-lock.yml +# conda-lock -f environment.yml -f ../../../../../../../tmp/tmpdb3jvs8h/environment.yml -f ../../../../../../../tmp/tmp_53rb1au/environment.yml --lockfile conda-lock.yml version: 1 metadata: content_hash: - linux-64: c13a9c3f6e5124634bbd3f593dd268427edd9f134964126a4bd008ea25ab4a59 - osx-64: 4ae28378b6cd07848bb1a2d05c1b4cb2e19eeea4d17f18115848ad653fa4296c - osx-arm64: fa9f44539d8d5eb68130e8220c1dcff4fd2b17de76df4adf70dbe5212218195d + linux-64: a6749a8d92855edd27304bd7a53c432641d7fddfc0f2ea840285120166e9463b + osx-64: f8cdfbf49b2dd534c1873edc292c88278c12e2dbae39754856c4c565ef4ff5b5 + osx-arm64: c9143412a53fe56ba327876a10b4bd0a1a4d441c5d40f370bf9124e550a61c06 channels: - url: conda-forge used_env_vars: [] @@ -24,8 +24,9 @@ metadata: - osx-64 - osx-arm64 sources: - - environment-13.yml - - ../../../../../../../tmp/tmp3d9y6g3q/environment.yml + - environment.yml + - ../../../../../../../tmp/tmpdb3jvs8h/environment.yml + - ../../../../../../../tmp/tmp_53rb1au/environment.yml package: - name: _libgcc_mutex version: '0.1' @@ -1996,48 +1997,48 @@ package: category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3 - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: - botocore: '>=1.40.18,<1.41.0' + botocore: '>=1.40.19,<1.41.0' jmespath: '>=0.7.1,<2.0.0' python: '>=3.10' s3transfer: '>=0.13.0,<0.14.0' - url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/boto3-1.40.19-pyhd8ed1ab_0.conda hash: - md5: fb19c785e87d1ecc97112f621aa9d4e6 - sha256: 118618c6d4868ffc4b05b67950626fd3743b782b0b7e7027aba65d73156d958b + md5: 75966ce58fc3dd08f288bdc04a65fdf2 + sha256: b1c026ad3da6b695ff6b2dfc49d0e0960e2aeeb5ac10e4e270af1073e5a6ff79 category: main optional: false - name: boto3-stubs @@ -2086,7 +2087,7 @@ package: category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: linux-64 dependencies: @@ -2094,14 +2095,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-64 dependencies: @@ -2109,14 +2110,14 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore - version: 1.40.18 + version: 1.40.19 manager: conda platform: osx-arm64 dependencies: @@ -2124,10 +2125,10 @@ package: python: '>=3.10' python-dateutil: '>=2.1,<3.0.0' urllib3: '>=1.25.4,!=2.2.0,<3' - url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.18-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/botocore-1.40.19-pyhd8ed1ab_0.conda hash: - md5: 42f86fbce14d7a025ff914cfc7e5450e - sha256: ae41db8e340a72fcf7f4804fbaa12c8c46da033637cfd205a6bf05a811ab40bb + md5: dd2d8556835c152e514523f2b2fcd339 + sha256: b6987587295370962c983a991c7a8fa678bd488c81fea30fc5045f4182f29807 category: main optional: false - name: botocore-stubs @@ -3476,7 +3477,7 @@ package: category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: linux-64 dependencies: @@ -3492,14 +3493,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.0-hc85cc9f_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/cmake-4.1.1-hc85cc9f_0.conda hash: - md5: 63080125641ce03edb003ba6cb3639d0 - sha256: e7f4837d1d74368bcda30aaae545af72fe8a83abd86666e0a56a6fcb744e6508 + md5: c090226f6d82c9bb396948065c3808d9 + sha256: 00f6c0883b8365e141a77d1777339817fe4c582e6cf1e39c69fb0b3eb4349b3a category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-64 dependencies: @@ -3514,14 +3515,14 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.0-h118fb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/cmake-4.1.1-h118fb26_0.conda hash: - md5: eeaba0b95ae7d9cf3834686f37f5ff2a - sha256: ceb6b5a8185534b1ca5d93d1ad36ca606afc7515e5b90a99f5d01c438f7e7664 + md5: 07d353575f84d6ccaa01fcdfd2d9db0c + sha256: 72d8ae208757d404a6dd2de94a1f9dd314dbb5f3c0f0ee89424a5444fb9e3386 category: main optional: false - name: cmake - version: 4.1.0 + version: 4.1.1 manager: conda platform: osx-arm64 dependencies: @@ -3536,10 +3537,10 @@ package: ncurses: '>=6.5,<7.0a0' rhash: '>=1.4.6,<2.0a0' zstd: '>=1.5.7,<1.6.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.0-hae74ae4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/cmake-4.1.1-hae74ae4_0.conda hash: - md5: 65d333c04dcdbea01b16993358df3364 - sha256: 0e00e9c1944e594f293e10b4e4abd4505f098d8d63c95c455b80775abcf134fa + md5: 8a19f6de15b62a0ad43fc5959e8bb325 + sha256: ec1d31c48cce2c94bd5ed29bb3af809845a32af255704c2cc87b6106e140b04a category: main optional: false - name: colorama @@ -4864,6 +4865,42 @@ package: sha256: ce61f4f99401a4bd455b89909153b40b9c823276aefcbb06f2044618696009ca category: main optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: linux-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false +- name: execnet + version: 2.1.1 + manager: conda + platform: osx-arm64 + dependencies: + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/execnet-2.1.1-pyhd8ed1ab_1.conda + hash: + md5: a71efeae2c160f6789900ba2631a2c90 + sha256: 9abc6c128cd40733e9b24284d0462e084d4aff6afe614f0754aa8533ebe505e4 + category: main + optional: false - name: executing version: 2.2.0 manager: conda @@ -5376,7 +5413,7 @@ package: category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: linux-64 dependencies: @@ -5386,14 +5423,14 @@ package: munkres: '' python: '>=3.13,<3.14.0a0' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.1-py313h3dea7bd_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/fonttools-4.59.2-py313h3dea7bd_0.conda hash: - md5: 649ea6ec13689862fae3baabec43534a - sha256: 5d7ea29adf8105dd6d9063fcd190fbd6e28f892bccc8d6bb0b6db8ce22d111a4 + md5: f3968013ee183bd2bce0e0433abd4384 + sha256: 1e2cac4460fd14b52324ce569428e0f2610fbc831c7271bdced3de4c92e62ae5 category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-64 dependencies: @@ -5402,14 +5439,14 @@ package: munkres: '' python: '>=3.13,<3.14.0a0' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.1-py313h4db2fa4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/fonttools-4.59.2-py313h4db2fa4_0.conda hash: - md5: 3a930d1619dbc7d00e199c92ab6e72e7 - sha256: ee1b6bdfcaa16de4597bd6b3de88da8d018bfe3b3f0ac8cdbd14012c921ff267 + md5: 0f0b289aa8a0d88d4823fa4a4f11eb93 + sha256: ffb36143ef728b0a490413a87114a80c966d22de8ec011568df2cda42731d265 category: main optional: false - name: fonttools - version: 4.59.1 + version: 4.59.2 manager: conda platform: osx-arm64 dependencies: @@ -5418,10 +5455,10 @@ package: munkres: '' python: '>=3.13,<3.14.0a0' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.1-py313ha0c97b7_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/fonttools-4.59.2-py313ha0c97b7_0.conda hash: - md5: f307ae7f719b67db601a78b035d6c447 - sha256: 1ddcac48360798372db89c5a4f39abdf277647a4f0b88af155a3651bc9079ef5 + md5: 8c978e51603fc2ce103a9501d355cfb8 + sha256: 2e5b6aa525762a1935e1b30f56a1934772f8b5d6aedceb48a464656313cd860f category: main optional: false - name: fqdn @@ -6640,45 +6677,45 @@ package: category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: linux-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h2 - version: 4.2.0 + version: 4.3.0 manager: conda platform: osx-arm64 dependencies: hpack: '>=4.1,<5' hyperframe: '>=6.1,<7' - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/h2-4.3.0-pyhcf101f3_0.conda hash: - md5: b4754fb1bdcb70c8fd54f918301582c6 - sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 + md5: 164fc43f0b53b6e3a7bc7dce5e4f1dc9 + sha256: 84c64443368f84b600bfecc529a1194a3b14c3656ee2e832d15a20e0329b6da3 category: main optional: false - name: h5netcdf @@ -6776,7 +6813,7 @@ package: category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: linux-64 dependencies: @@ -6791,14 +6828,14 @@ package: libglib: '>=2.84.3,<3.0a0' libstdcxx: '>=14' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.3-h15599e2_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-11.4.4-h15599e2_0.conda hash: - md5: e8d443a6375b0b266f0cb89ce22ccaa2 - sha256: 76bd39d9dbb2c982e017313a5c9163bdd2dfd95677fe05d1ea08edbed26de0e6 + md5: a0bddb46e3b740e019b4194e66a9c1fc + sha256: d4818bc75f840db25ba1a0025cfbfe6dd6527eab7a374b25556a09d59d75a7d3 category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-64 dependencies: @@ -6812,14 +6849,14 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.3-h0ffbb26_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/harfbuzz-11.4.4-h0ffbb26_0.conda hash: - md5: 5c5f8a58b6a3f50c17bc74011c0879f8 - sha256: 97bea280c0ee8898d0165e95af4752c0de7de572f5bebaeb180d4b57f02999ad + md5: 2bf7ee55fbaa26d73da8923594b172bb + sha256: 1955fa7ca36ea781da859535366c079454ba373f5ec60825a8e7c44485a9bc2b category: main optional: false - name: harfbuzz - version: 11.4.3 + version: 11.4.4 manager: conda platform: osx-arm64 dependencies: @@ -6833,10 +6870,10 @@ package: libfreetype6: '>=2.13.3' libglib: '>=2.84.3,<3.0a0' libzlib: '>=1.3.1,<2.0a0' - url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.3-hf4e55d4_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/harfbuzz-11.4.4-hf4e55d4_0.conda hash: - md5: e577ca64b17624c0382ac19c719f72bc - sha256: e3dd1c98f4f8cb27b3218fad87e1db31d2db0df8f5cc5a23d2f67858e065366a + md5: b7c5258c0c5427e0b115d517a1ed9e2c + sha256: 83ae631d8906677e60c39a6a5ef5e2dad36bffa7b55a62400f7dc79e10ceb7b4 category: main optional: false - name: hdf4 @@ -8129,42 +8166,42 @@ package: category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: joblib - version: 1.5.1 + version: 1.5.2 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' setuptools: '' - url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.1-pyhd8ed1ab_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.2-pyhd8ed1ab_0.conda hash: - md5: fb1c14694de51a476ce8636d92b6f42c - sha256: e5a4eca9a5d8adfaa3d51e24eefd1a6d560cb3b33a7e1eee13e410bec457b7ed + md5: 4e717929cfa0d49cef92d911e31d0e90 + sha256: 6fc414c5ae7289739c2ba75ff569b79f72e38991d61eb67426a8a4b92f90462c category: main optional: false - name: json-c @@ -12292,10 +12329,10 @@ package: libgcc: '>=14' openldap: '>=2.6.10,<2.7.0a0' openssl: '>=3.5.2,<4.0a0' - url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/libpq-17.6-h3675c94_1.conda hash: - md5: de8839c8dde1cba9335ac43d86e16d65 - sha256: 56ba34c2b3ae008a6623a59b14967366b296d884723ace95596cc986d31594a0 + md5: bcee8587faf5dce5050a01817835eaed + sha256: 1b3323f5553db17cad2b0772f6765bf34491e752bfe73077977d376679f97420 category: main optional: false - name: libprotobuf @@ -15314,12 +15351,12 @@ package: libgcc: '>=14' liblapack: '>=3.9.0,<4.0a0' libstdcxx: '>=14' - python: '>=3.13,<3.14.0a0' + python: '' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py313h1f731e6_1.conda + url: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.3.2-py313hf6604e3_2.conda hash: - md5: 2021e753ba08cd5476feacd87f03c2ad - sha256: 67b87ad8e36946a9d25a409451ebfd7d7b5d3087fa4406eb2e503dcc46080be5 + md5: 67d27f74a90f5f0336035203f91a0abc + sha256: dc99944cc1ab9b1939fc94ca0ad3e7629ff4b8fd371a5c94a153e6a66af5aa0d category: main optional: false - name: numpy @@ -15332,12 +15369,12 @@ package: libcblas: '>=3.9.0,<4.0a0' libcxx: '>=19' liblapack: '>=3.9.0,<4.0a0' - python: '>=3.13,<3.14.0a0' + python: '' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py313h333cfc4_1.conda + url: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.3.2-py313hdb1a8e5_2.conda hash: - md5: 24af56095c0f1be9e4bb5e949e1477f2 - sha256: 2ebddee209309c28a51038526aa190a1b2e344b073fe2a9ba27d0cf20291e6fe + md5: 87843ce61a6baf2cb0d7fad97433f704 + sha256: 0979cd27685e5c8767547e7dbc7ec5015b8080d85d4f43dd4318d9beb99fd98f category: main optional: false - name: numpy @@ -15350,12 +15387,12 @@ package: libcblas: '>=3.9.0,<4.0a0' libcxx: '>=19' liblapack: '>=3.9.0,<4.0a0' - python: '>=3.13,<3.14.0a0' + python: 3.13.* python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py313haac90e2_1.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.3.2-py313h674b998_2.conda hash: - md5: 6a551f055c64c64ff63a7c79e8e56342 - sha256: ac352170c22707cf756c069789756af65282a3fe5f2b15ff272876a691c3e101 + md5: 9c44ae43d006497eef4ba440820f9997 + sha256: f3603c74f79a9f8a67eb8f4ce4b678036ca3de6dd329657ae19a298cd956f66e category: main optional: false - name: openjpeg @@ -16590,42 +16627,42 @@ package: category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: linux-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: prompt-toolkit - version: 3.0.51 + version: 3.0.52 manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' + python: '>=3.10' wcwidth: '' - url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.51-pyha770c72_0.conda + url: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.52-pyha770c72_0.conda hash: - md5: d17ae9db4dc594267181bd199bf9a551 - sha256: ebc1bb62ac612af6d40667da266ff723662394c0ca78935340a5b5c14831227b + md5: edb16f14d920fb3faf17f5ce582942d6 + sha256: 4817651a276016f3838957bfdf963386438c70761e9faec7749d411635979bae category: main optional: false - name: psutil @@ -17881,6 +17918,48 @@ package: sha256: 25afa7d9387f2aa151b45eb6adf05f9e9e3f58c8de2bc09be7e85c114118eeb9 category: main optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: linux-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false +- name: pytest-xdist + version: 3.8.0 + manager: conda + platform: osx-arm64 + dependencies: + execnet: '>=2.1' + pytest: '>=7.0.0' + python: '>=3.9' + url: https://conda.anaconda.org/conda-forge/noarch/pytest-xdist-3.8.0-pyhd8ed1ab_0.conda + hash: + md5: 8375cfbda7c57fbceeda18229be10417 + sha256: b7b58a5be090883198411337b99afb6404127809c3d1c9f96e99b59f36177a96 + category: main + optional: false - name: python version: 3.13.5 manager: conda @@ -19147,7 +19226,7 @@ package: category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: linux-64 dependencies: @@ -19155,38 +19234,38 @@ package: libgcc: '>=14' python: '' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.0-py313h843e2db_0.conda + url: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-0.27.1-py313h843e2db_0.conda hash: - md5: 4126b8e1fcfaebfead4e059f64b16996 - sha256: e6ed8b8fa2a3280663ebf3c599cfff134ce8db1e77864f5f735c74e4e55601e7 + md5: f713aec06900657c138e60dc61889557 + sha256: 64e5748673d41f055ff4ca5e7a720e9a0a6122f7b2954d21d427f247b9d3e2b8 category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-64 dependencies: __osx: '>=10.13' python: '' python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.0-py313h66e1e84_0.conda + url: https://conda.anaconda.org/conda-forge/osx-64/rpds-py-0.27.1-py313h66e1e84_0.conda hash: - md5: 0ea5ad38eb07898fae8325b5fbe31ec4 - sha256: 9ddf04ae730fd4d3805f8fd22017cdb21e8eeb12aeda8f3ccfc513843cc6c2cb + md5: 1c76a37ff2400bc40a83ef4c765eaf5c + sha256: 8c5ffeca70230f1a17f7a77c4e076630d6f7b6f3d22b5d3a876947de9474c072 category: main optional: false - name: rpds-py - version: 0.27.0 + version: 0.27.1 manager: conda platform: osx-arm64 dependencies: __osx: '>=11.0' python: 3.13.* python_abi: 3.13.* - url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.0-py313h80e0809_0.conda + url: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-0.27.1-py313h80e0809_0.conda hash: - md5: d7b4225434fffea78af14273a12dbcee - sha256: d05d4eb509beb6a8061793a5710f4f9dffad98284bb0ace9a3f21b63102c78a6 + md5: 853010644628104ea462f6458a7e0a44 + sha256: a4bd0f620cdab3760b7f8a39248ce974187c160f8cd54860f2d8674d75e7d711 category: main optional: false - name: ruamel.yaml @@ -19965,39 +20044,39 @@ package: category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: linux-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: soupsieve - version: '2.7' + version: '2.8' manager: conda platform: osx-arm64 dependencies: - python: '>=3.9' - url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.7-pyhd8ed1ab_0.conda + python: '>=3.10' + url: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.8-pyhd8ed1ab_0.conda hash: - md5: fb32097c717486aa34b38a9db57eb49e - sha256: 7518506cce9a736042132f307b3f4abce63bf076f5fb07c1f4e506c0b214295a + md5: 18c019ccf43769d211f2cf78e9ad46c2 + sha256: c978576cf9366ba576349b93be1cfd9311c00537622a2f9e14ba2b90c97cae9c category: main optional: false - name: sqlite diff --git a/environment.yml b/environment.yml index 1e39ddd85..2af02b4cf 100644 --- a/environment.yml +++ b/environment.yml @@ -53,6 +53,7 @@ dependencies: - pytest-cov - pytest-mock - pytest-timeout + - pytest-xdist - setuptools_scm>=6.2 - sysroot_linux-64 # For docs website diff --git a/setup.py b/setup.py index 9d18af2df..103200889 100755 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ from Cython.Build import cythonize # isort:skip # Parameter defs -UTIL_DIR = Path('tools') / 'bindings' / 'utils' +UTIL_DIR = Path('tools/bindings/utils') pybind_extensions = [ Pybind11Extension( diff --git a/test/__init__.py b/test/__init__.py index 71ef256d8..a74bd96aa 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,12 +1,12 @@ import os -import string import random -from contextlib import contextmanager +import string from pathlib import Path import numpy as np import xarray as xr + test_dir = Path(__file__).parents[0] TEST_DIR = test_dir.absolute() @@ -17,16 +17,6 @@ WM = 'MERRA2' -@contextmanager -def pushd(dir): - """ - Change the current working directory within a context. - """ - prevdir = os.getcwd() - os.chdir(dir) - yield - os.chdir(prevdir) - def makeLatLonGrid(bbox, reg, out_dir, spacing=0.1): """ Make lat lons at a specified spacing """ diff --git a/test/credentials/test_createFile.py b/test/credentials/test_createFile.py index 5eed69c80..c43ca93f2 100644 --- a/test/credentials/test_createFile.py +++ b/test/credentials/test_createFile.py @@ -56,13 +56,13 @@ def get_creds_netrc(rc_path: Path) -> Tuple[str, str]: ('MERRA2', get_creds_netrc, True), ), ) -def test_createFile(model_name, get_creds, has_uid): +def test_createFile(tmp_path: Path, model_name, get_creds, has_uid): # Get the rc file's path hidden_ext = '_' if system() == 'Windows' else '.' rc_filename = credentials.RC_FILENAMES[model_name] if rc_filename is None: return - rc_path = Path('./') / (hidden_ext + rc_filename) + rc_path = tmp_path / (hidden_ext + rc_filename) rc_path = rc_path.expanduser() rc_path.unlink(missing_ok=True) @@ -70,7 +70,7 @@ def test_createFile(model_name, get_creds, has_uid): test_key = random_string() # Test creation of the rc file - credentials.check_api(model_name, test_uid, test_key, './', update_rc_file=False) + credentials.check_api(model_name, test_uid, test_key, tmp_path, update_rc_file=False) assert rc_path.exists(), f'{rc_path} does not exist' # Check if API is written correctly diff --git a/test/credentials/test_envVars.py b/test/credentials/test_envVars.py index 8396f3811..e2d8fe01a 100644 --- a/test/credentials/test_envVars.py +++ b/test/credentials/test_envVars.py @@ -79,6 +79,7 @@ ] ) def test_envVars( + tmp_path: Path, monkeypatch, model_name, template, @@ -89,7 +90,7 @@ def test_envVars( rc_filename = credentials.RC_FILENAMES[model_name] if rc_filename is None: return - rc_path = Path('./') / (hidden_ext + rc_filename) + rc_path = tmp_path / (hidden_ext + rc_filename) rc_path = rc_path.expanduser() rc_path.unlink(missing_ok=True) @@ -102,7 +103,7 @@ def test_envVars( with monkeypatch.context() as mp: mp.setenv(env_var_name_uid, test_uid) mp.setenv(env_var_name_key, test_key) - credentials.check_api(model_name, None, None, './', update_rc_file=True) + credentials.check_api(model_name, None, None, tmp_path, update_rc_file=True) expected_content = template.format(uid=test_uid, key=test_key) actual_content = rc_path.read_text() diff --git a/test/credentials/test_updateFalse.py b/test/credentials/test_updateFalse.py index 822c58ff9..d81cf47f3 100644 --- a/test/credentials/test_updateFalse.py +++ b/test/credentials/test_updateFalse.py @@ -2,28 +2,29 @@ When update_rc_file is False, the RC file should NOT be modified if it already exists. ''' -import pytest - from pathlib import Path from platform import system + +import pytest + from RAiDER.models import credentials @pytest.mark.parametrize('model_name', 'ERA5 ERA5T HRES GMAO MERRA2'.split()) -def test_updateFalse(model_name): +def test_updateFalse(tmp_path: Path, model_name): # Get the rc file's path hidden_ext = '_' if system() == "Windows" else '.' rc_filename = credentials.RC_FILENAMES[model_name] if rc_filename is None: return - rc_path = Path('./') / (hidden_ext + rc_filename) + rc_path = tmp_path / (hidden_ext + rc_filename) rc_path = rc_path.expanduser() # Write some example text to test for rc_path.write_text('dummy') # Test creation of this model's RC file in current dir - credentials.check_api(model_name, None, None, './', update_rc_file=False) + credentials.check_api(model_name, None, None, tmp_path, update_rc_file=False) # Assert the content was unchanged content = rc_path.read_text() diff --git a/test/credentials/test_updateTrue.py b/test/credentials/test_updateTrue.py index 6fb5662ed..597e07e95 100644 --- a/test/credentials/test_updateTrue.py +++ b/test/credentials/test_updateTrue.py @@ -70,13 +70,13 @@ ), ] ) -def test_updateTrue(model_name, template) -> None: +def test_updateTrue(tmp_path: Path, model_name, template) -> None: # Get the rc file's path hidden_ext = '_' if system() == "Windows" else '.' rc_filename = credentials.RC_FILENAMES[model_name] if rc_filename is None: return - rc_path = Path('./') / (hidden_ext + rc_filename) + rc_path = tmp_path / (hidden_ext + rc_filename) # Give the rc file mock contents rc_path.write_text(template.format(uid=random_string(), key=random_string())) @@ -84,7 +84,7 @@ def test_updateTrue(model_name, template) -> None: # Use check_api to update the rc file test_uid = random_string() test_key = random_string() - credentials.check_api(model_name, test_uid, test_key, './', update_rc_file=True) + credentials.check_api(model_name, test_uid, test_key, tmp_path, update_rc_file=True) # Check that the rc file was properly updated expected_content = template.format(uid=test_uid, key=test_key) diff --git a/test/scenario_1/raider_example_1.yaml b/test/scenario_1/raider_example_1.yaml deleted file mode 100644 index 7305a400d..000000000 --- a/test/scenario_1/raider_example_1.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# vim: set filetype=yaml: - look_dir: right - date_group: # Groups mean that there are several different possible options, see documentation for details - date_start: 20200101 - time_group: - time: "12:00:00" # UTC time as a string - interpolate_time: 'none' - weather_model: HRRR # RAiDER supports several models, including ECMWF, HRRR, GMAO, and MERRA-2 - aoi_group: - bounding_box: 36 37 -92 -91 # bounding box uses the weather model grid nodes - height_group: - height_levels: 0 50 100 500 1000 # Return only these specific height levels - los_group: # absent other options ZTD is calculated diff --git a/test/test_GUNW.py b/test/test_GUNW.py index 633559076..f07d3d739 100644 --- a/test/test_GUNW.py +++ b/test/test_GUNW.py @@ -1,9 +1,6 @@ -import glob import json -import os import shutil import unittest - from datetime import datetime from pathlib import Path @@ -13,39 +10,49 @@ import pytest import rasterio as rio import xarray as xr +from pytest_mock import MockerFixture import RAiDER +import RAiDER.aria.prepFromGUNW import RAiDER.cli.raider as raider import RAiDER.s1_azimuth_timing from RAiDER import aws -import RAiDER.aria.prepFromGUNW from RAiDER.aria.prepFromGUNW import ( - check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation, - check_weather_model_availability,_get_acq_time_from_gunw_id, - get_slc_ids_from_gunw,get_acq_time_from_slc_id,identify_which_hrrr + _get_acq_time_from_gunw_id, + check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation, + check_weather_model_availability, + get_acq_time_from_slc_id, + get_slc_ids_from_gunw, + identify_which_hrrr, ) from RAiDER.cli.raider import calcDelaysGUNW -from RAiDER.models.hrrr import HRRR, HRRRAK from RAiDER.models.customExceptions import ( - NoWeatherModelData, WrongNumberOfFiles, -) + NoWeatherModelData, + WrongNumberOfFiles, +) +from RAiDER.models.hrrr import HRRR, HRRRAK def compute_transform(lats, lons): - """ Hand roll an affine transform from lat/lon coords """ + """Hand roll an affine transform from lat/lon coords.""" a = lons[1] - lons[0] # lon spacing b = 0 - c = lons[0] - a/2 # lon start, adjusted by half a grid cell + c = lons[0] - a / 2 # lon start, adjusted by half a grid cell d = 0 e = lats[1] - lats[0] - f = lats[0] - e/2 + f = lats[0] - e / 2 return (a, b, c, d, e, f) @pytest.mark.isce3 @pytest.mark.parametrize('weather_model_name', ['GMAO']) -def test_GUNW_dataset_update(test_dir_path, test_gunw_path_factory, weather_model_name, - weather_model_dict_for_gunw_integration_test, mocker): +def test_GUNW_dataset_update( + tmp_path: Path, + test_gunw_path_factory, + weather_model_name, + weather_model_dict_for_gunw_integration_test, + mocker: MockerFixture, +) -> None: """The GUNW from test gunw factor is: S1-GUNW-D-R-071-tops-20200130_20200124-135156-34956N_32979N-PP-913f-v2_0_4.nc @@ -53,21 +60,26 @@ def test_GUNW_dataset_update(test_dir_path, test_gunw_path_factory, weather_mode Therefore relevant GMAO datetimes are 12 pm and 3 pm (in that order) """ - scenario_dir = test_dir_path / 'GUNW' + scenario_dir = tmp_path / 'GUNW' scenario_dir.mkdir(exist_ok=True, parents=True) orig_GUNW = test_gunw_path_factory() updated_GUNW = scenario_dir / orig_GUNW.name shutil.copy(orig_GUNW, updated_GUNW) - iargs = ['--weather-model', weather_model_name, - '--file', str(updated_GUNW), - '-interp', 'center_time'] + # fmt: off + iargs = [ + '--weather-model', weather_model_name, + '--file', str(updated_GUNW), + '-interp', 'center_time', + '--output-directory', str(scenario_dir), + ] + # fmt: on side_effect = weather_model_dict_for_gunw_integration_test[weather_model_name] # RAiDER needs strings for paths side_effect = list(map(str, side_effect)) - mocker.patch('RAiDER.processWM.prepareWeatherModel', - side_effect=side_effect) + mocker.patch('RAiDER.processWM.prepareWeatherModel', side_effect=side_effect) + calcDelaysGUNW(iargs) # check the CRS and affine are written correctly @@ -85,13 +97,8 @@ def test_GUNW_dataset_update(test_dir_path, test_gunw_path_factory, weather_mode crs = rio.crs.CRS.from_wkt(ds['crs'].crs_wkt) assert crs.to_epsg() == epsg, 'CRS incorrect' - # Clean up files - shutil.rmtree(scenario_dir) - os.remove('GUNW_20200130-20200124_135156.yaml') - [os.remove(f) for f in glob.glob(f'{weather_model_name}*')] - -def test_GUNW_hyp3_metadata_update(test_gunw_json_path, test_gunw_json_schema_path, tmp_path, mocker): +def test_GUNW_hyp3_metadata_update(test_gunw_json_path, test_gunw_json_schema_path, tmp_path: Path, mocker: MockerFixture) -> None: """This test performs the GUNW entrypoint with bucket/prefix provided and only updates the json. Monkey patches the upload/download to/from s3 and the actual computation. """ @@ -103,22 +110,27 @@ def test_GUNW_hyp3_metadata_update(test_gunw_json_path, test_gunw_json_schema_pa mocker.patch("RAiDER.aws.get_s3_file", side_effect=[Path('foo.nc'), temp_json_path, Path('foo.png')]) mocker.patch("RAiDER.aws.upload_file_to_s3") mocker.patch("RAiDER.aria.prepFromGUNW.main", return_value=['my_path_cfg', 'my_wavelength']) - mocker.patch('RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation', + mocker.patch('RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation', side_effect=[True]) mocker.patch("RAiDER.aria.prepFromGUNW.check_weather_model_availability", return_value=True) mocker.patch("RAiDER.cli.raider.calcDelays", return_value=['file1', 'file2']) mocker.patch("RAiDER.aria.calcGUNW.tropo_gunw_slc") - iargs = ['--weather-model', 'HRES', - '--bucket', 'myBucket', - '--bucket-prefix', 'myPrefix',] + # fmt: off + iargs = [ + '--weather-model', 'HRES', + '--bucket', 'myBucket', + '--bucket-prefix', 'myPrefix', + '--output-directory', str(tmp_path), + ] + # fmt: on calcDelaysGUNW(iargs) metadata = json.loads(temp_json_path.read_text()) schema = json.loads(test_gunw_json_schema_path.read_text()) assert metadata['metadata']['weather_model'] == ['HRES'] - assert (jsonschema.validate(instance=metadata, schema=schema) is None) + assert jsonschema.validate(instance=metadata, schema=schema) is None assert aws.get_s3_file.mock_calls == [ unittest.mock.call('myBucket', 'myPrefix', '.nc'), @@ -143,7 +155,12 @@ def test_GUNW_hyp3_metadata_update(test_gunw_json_path, test_gunw_json_schema_pa ] -def test_GUNW_hyp3_metadata_update_different_prefix(test_gunw_json_path, test_gunw_json_schema_path, tmp_path, mocker): +def test_GUNW_hyp3_metadata_update_different_prefix( + test_gunw_json_path: Path, + test_gunw_json_schema_path: Path, + tmp_path: Path, + mocker: MockerFixture, +) -> None: """This test performs the GUNW entrypoint using a different input bucket/prefix than the output bucket/prefix. Only updates the json. Monkey patches the upload/download to/from s3 and the actual computation. """ @@ -152,26 +169,33 @@ def test_GUNW_hyp3_metadata_update_different_prefix(test_gunw_json_path, test_gu # We only need to make sure the json file is passed, the netcdf file name will not have # any impact on subsequent testing - mocker.patch("RAiDER.aws.get_s3_file", side_effect=['foo.nc', temp_json_path, 'foo.png']) - mocker.patch("RAiDER.aws.upload_file_to_s3") - mocker.patch("RAiDER.aria.prepFromGUNW.main", return_value=['my_path_cfg', 'my_wavelength']) - mocker.patch('RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation', - side_effect=[True]) - mocker.patch("RAiDER.aria.prepFromGUNW.check_weather_model_availability", return_value=True) - mocker.patch("RAiDER.cli.raider.calcDelays", return_value=['file1', 'file2']) - mocker.patch("RAiDER.aria.calcGUNW.tropo_gunw_slc") + mocker.patch('RAiDER.aws.get_s3_file', side_effect=['foo.nc', temp_json_path, 'foo.png']) + mocker.patch('RAiDER.aws.upload_file_to_s3') + mocker.patch('RAiDER.aria.prepFromGUNW.main', return_value=['my_path_cfg', 'my_wavelength']) + mocker.patch( + 'RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation', + side_effect=[True], + ) + mocker.patch('RAiDER.aria.prepFromGUNW.check_weather_model_availability', return_value=True) + mocker.patch('RAiDER.cli.raider.calcDelays', return_value=['file1', 'file2']) + mocker.patch('RAiDER.aria.calcGUNW.tropo_gunw_slc') - iargs = ['--weather-model', 'HRES', - '--bucket', 'myBucket', - '--bucket-prefix', 'myOutputPrefix', - '--input-bucket-prefix', 'myInputPrefix',] + # fmt: off + iargs = [ + '--weather-model', 'HRES', + '--bucket', 'myBucket', + '--bucket-prefix', 'myOutputPrefix', + '--input-bucket-prefix', 'myInputPrefix', + '--output-directory', str(tmp_path), + ] + # fmt: on calcDelaysGUNW(iargs) metadata = json.loads(temp_json_path.read_text()) schema = json.loads(test_gunw_json_schema_path.read_text()) assert metadata['metadata']['weather_model'] == ['HRES'] - assert (jsonschema.validate(instance=metadata, schema=schema) is None) + assert jsonschema.validate(instance=metadata, schema=schema) is None assert aws.get_s3_file.mock_calls == [ unittest.mock.call('myBucket', 'myInputPrefix', '.nc'), @@ -197,13 +221,15 @@ def test_GUNW_hyp3_metadata_update_different_prefix(test_gunw_json_path, test_gu @pytest.mark.parametrize('weather_model_name', ['HRRR']) -def test_azimuth_timing_interp_against_center_time_interp(weather_model_name: str, - tmp_path: Path, - gunw_azimuth_test: Path, - orbit_dict_for_azimuth_time_test: dict[str], - weather_model_dict_for_azimuth_time_test, - weather_model_dict_for_center_time_test, - mocker): +def test_azimuth_timing_interp_against_center_time_interp( + weather_model_name: str, + tmp_path: Path, + gunw_azimuth_test: Path, + orbit_dict_for_azimuth_time_test: dict[str, Path], + weather_model_dict_for_azimuth_time_test, + weather_model_dict_for_center_time_test, + mocker: MockerFixture, +) -> None: """This test shows that the azimuth timing interpolation does not deviate from the center time by more than 1 mm for the HRRR model. This is expected since the model times are 6 hours apart and a the azimuth time is changing the interpolation weights for a given pixel at the order @@ -246,7 +272,6 @@ def test_azimuth_timing_interp_against_center_time_interp(weather_model_name: st sec: S1B_IW_SLC__1SDV_20210711T015011_20210711T015038_027740_034F80_376C, S1B_IW_SLC__1SDV_20210711T014922_20210711T014949_027740_034F80_859D """ - out_0 = gunw_azimuth_test.name.replace('.nc', '__ct_interp.nc') out_1 = gunw_azimuth_test.name.replace('.nc', '__az_interp.nc') @@ -262,14 +287,16 @@ def test_azimuth_timing_interp_against_center_time_interp(weather_model_name: st # f77af9ce2d3875b00730603305c0e92d6c83adc2/tools/RAiDER/aria/prepFromGUNW.py#L151-L200 # These outputs are not needed since the orbits are specified above - mocker.patch('RAiDER.s1_azimuth_timing.get_slc_id_from_point_and_time', - side_effect=[ - # Azimuth time - ['reference_slc_id'], - # using two "dummy" ids to mimic GUNW sec granules - # See docstring - ['secondary_slc_id', 'secondary_slc_id'], - ]) + mocker.patch( + 'RAiDER.s1_azimuth_timing.get_slc_id_from_point_and_time', + side_effect=[ + # Azimuth time + ['reference_slc_id'], + # using two "dummy" ids to mimic GUNW sec granules + # See docstring + ['secondary_slc_id', 'secondary_slc_id'], + ], + ) mocker.patch( 'RAiDER.s1_azimuth_timing.get_orbits_from_slc_ids', @@ -277,27 +304,39 @@ def test_azimuth_timing_interp_against_center_time_interp(weather_model_name: st # For azimuth time [str(orbit_dict_for_azimuth_time_test['reference'])], [str(orbit_dict_for_azimuth_time_test['secondary']), str(orbit_dict_for_azimuth_time_test['secondary'])], - ] + ], ) - side_effect = (weather_model_dict_for_center_time_test[weather_model_name] + - weather_model_dict_for_azimuth_time_test[weather_model_name]) + side_effect = ( + weather_model_dict_for_center_time_test[weather_model_name] + + weather_model_dict_for_azimuth_time_test[weather_model_name] + ) # RAiDER needs strings for paths side_effect = list(map(str, side_effect)) - mocker.patch('RAiDER.processWM.prepareWeatherModel', - side_effect=side_effect) + mocker.patch('RAiDER.processWM.prepareWeatherModel', side_effect=side_effect) + + # fmt: off + path_0 = tmp_path / '0' + path_0.mkdir() iargs_0 = [ - '--file', str(out_path_0), - '--weather-model', weather_model_name, - '-interp', 'center_time' - ] + '--file', str(out_path_0), + '--weather-model', weather_model_name, + '-interp', 'center_time', + '--output-directory', str(path_0), + ] + # fmt: on calcDelaysGUNW(iargs_0) + path_1 = tmp_path / '1' + path_1.mkdir() + # fmt: off iargs_1 = [ - '--file', str(out_path_1), - '--weather-model', weather_model_name, - '-interp', 'azimuth_time_grid' - ] + '--file', str(out_path_1), + '--weather-model', weather_model_name, + '-interp', 'azimuth_time_grid', + '--output-directory', str(path_1), + ] + # fmt: on calcDelaysGUNW(iargs_1) # Calls 4 times for azimuth time and 4 times for center time @@ -321,15 +360,17 @@ def test_azimuth_timing_interp_against_center_time_interp(weather_model_name: st @pytest.mark.parametrize('weather_model_name', ['HRRR', 'HRES', 'ERA5', 'ERA5T']) -def test_check_weather_model_availability(test_gunw_path_factory, weather_model_name, mocker): +def test_check_weather_model_availability(test_gunw_path_factory, weather_model_name, mocker: MockerFixture) -> None: # Should be True for all weather models # S1-GUNW-D-R-071-tops-20200130_20200124-135156-34956N_32979N-PP-913f-v2_0_4.nc test_gunw_path = test_gunw_path_factory() assert check_weather_model_availability(test_gunw_path, weather_model_name) # Let's mock an earlier date for some models - mocker.patch("RAiDER.aria.prepFromGUNW.get_acq_time_from_slc_id", side_effect=[pd.Timestamp('2015-01-01'), - pd.Timestamp('2014-01-01')]) + mocker.patch( + 'RAiDER.aria.prepFromGUNW.get_acq_time_from_slc_id', + side_effect=[pd.Timestamp('2015-01-01'), pd.Timestamp('2014-01-01')], + ) cond = check_weather_model_availability(test_gunw_path, weather_model_name) if weather_model_name in ['HRRR', 'MERRA2']: cond = not cond @@ -337,69 +378,80 @@ def test_check_weather_model_availability(test_gunw_path_factory, weather_model_ @pytest.mark.parametrize('weather_model_name', ['HRRR']) -def test_check_weather_model_availability_over_alaska(test_gunw_path_factory, weather_model_name, mocker): +def test_check_weather_model_availability_over_alaska(test_gunw_path_factory, weather_model_name, mocker: MockerFixture) -> None: # Should be True for all weather models # S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc test_gunw_path = test_gunw_path_factory(location='alaska') assert check_weather_model_availability(test_gunw_path, weather_model_name) # Let's mock an earlier date - mocker.patch("RAiDER.aria.prepFromGUNW.get_acq_time_from_slc_id", side_effect=[pd.Timestamp('2017-01-01'), - pd.Timestamp('2016-01-01')]) + mocker.patch( + 'RAiDER.aria.prepFromGUNW.get_acq_time_from_slc_id', + side_effect=[pd.Timestamp('2017-01-01'), pd.Timestamp('2016-01-01')], + ) cond = check_weather_model_availability(test_gunw_path, weather_model_name) if weather_model_name == 'HRRR': cond = not cond assert cond -def test_check_hrrr_availability_outside_united_states(test_gunw_path_factory): +def test_check_hrrr_availability_outside_united_states(test_gunw_path_factory) -> None: # S1-GUNW-D-R-032-tops-20200220_20200214-214625-00120E_00014N-PP-b785-v3_0_1.nc test_gunw_path = test_gunw_path_factory(location='philippines') assert not check_weather_model_availability(test_gunw_path, 'HRRR') @pytest.mark.parametrize('weather_model_name', ['ERA5', 'GMAO', 'MERRA2', 'HRRR']) -def test_check_weather_model_availability_2(weather_model_name): - gunw_id = Path("test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc") +def test_check_weather_model_availability_2(weather_model_name) -> None: + gunw_id = Path('test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc') assert check_weather_model_availability(gunw_id, weather_model_name) -def test_check_weather_model_availability_3(): - gunw_id = Path("test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc") +def test_check_weather_model_availability_3() -> None: + gunw_id = Path('test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc') with pytest.raises(ValueError): check_weather_model_availability(gunw_id, 'NotAModel') @pytest.mark.parametrize('weather_model_name', ['HRRR']) @pytest.mark.parametrize('location', ['california-t71', 'alaska']) -def test_weather_model_availability_integration_using_valid_range(location, - test_gunw_path_factory, - tmp_path, - weather_model_name, - mocker): +def test_weather_model_availability_integration_using_valid_range( + location, + test_gunw_path_factory, + tmp_path: Path, + weather_model_name, + mocker: MockerFixture, +) -> None: temp_json_path = tmp_path / 'temp.json' test_gunw_path = test_gunw_path_factory(location=location) shutil.copy(test_gunw_path, temp_json_path) # We will pass the test GUNW to the workflow - mocker.patch("RAiDER.aws.get_s3_file", side_effect=[test_gunw_path, 'foo.json']) - mocker.patch("RAiDER.aws.upload_file_to_s3") + mocker.patch('RAiDER.aws.get_s3_file', side_effect=[test_gunw_path, 'foo.json']) + mocker.patch('RAiDER.aws.upload_file_to_s3') # Have another test for checking the actual files - we are only checking for valid if weather_model_name == 'HRRR': - mocker.patch('RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation', - side_effect=[True]) + mocker.patch( + 'RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation', + side_effect=[True], + ) # These are outside temporal availability of GMAO and HRRR ref_date, sec_date = pd.Timestamp('2015-01-01'), pd.Timestamp('2014-01-01') - mocker.patch("RAiDER.aria.prepFromGUNW.get_acq_time_from_slc_id", side_effect=[ref_date, sec_date]) + mocker.patch('RAiDER.aria.prepFromGUNW.get_acq_time_from_slc_id', side_effect=[ref_date, sec_date]) # Don't specify side-effects or return values, because never called - mocker.patch("RAiDER.aria.prepFromGUNW.main") - mocker.patch("RAiDER.cli.raider.calcDelays") - mocker.patch("RAiDER.aria.calcGUNW.tropo_gunw_slc") + mocker.patch('RAiDER.aria.prepFromGUNW.main') + mocker.patch('RAiDER.cli.raider.calcDelays') + mocker.patch('RAiDER.aria.calcGUNW.tropo_gunw_slc') - iargs = ['--weather-model', weather_model_name, - '--bucket', 'myBucket', - '--bucket-prefix', 'myPrefix'] + # fmt: off + iargs = [ + '--weather-model', weather_model_name, + '--bucket', 'myBucket', + '--bucket-prefix', 'myPrefix', + '--output-directory', str(tmp_path), + ] + # fmt: on out = calcDelaysGUNW(iargs) # Check it returned None assert out is None @@ -412,61 +464,72 @@ def test_weather_model_availability_integration_using_valid_range(location, @pytest.mark.parametrize('weather_model_name', ['HRRR']) @pytest.mark.parametrize('interp_method', ['center_time', 'azimuth_time_grid']) -def test_provenance_metadata_for_tropo_group(weather_model_name: str, - tmp_path: Path, - gunw_azimuth_test: Path, - orbit_dict_for_azimuth_time_test: dict[str], - weather_model_dict_for_azimuth_time_test, - weather_model_dict_for_center_time_test, - interp_method, - mocker): +def test_provenance_metadata_for_tropo_group( + weather_model_name: str, + tmp_path: Path, + gunw_azimuth_test: Path, + orbit_dict_for_azimuth_time_test: dict[str, Path], + weather_model_dict_for_azimuth_time_test, + weather_model_dict_for_center_time_test, + interp_method, + mocker: MockerFixture, +) -> None: """ Same mocks as `test_azimuth_timing_interp_against_center_time_interp` above. """ - out = gunw_azimuth_test.name.replace('.nc', '__ct_interp.nc') out_path = shutil.copy(gunw_azimuth_test, tmp_path / out) if interp_method == 'azimuth_time_grid': # These outputs are not needed since the orbits are specified above - mocker.patch('RAiDER.s1_azimuth_timing.get_slc_id_from_point_and_time', - side_effect=[ - # Azimuth time - ['reference_slc_id'], - # using two "dummy" ids to mimic GUNW sec granules - # See docstring - ['secondary_slc_id', 'secondary_slc_id'], - ]) + mocker.patch( + 'RAiDER.s1_azimuth_timing.get_slc_id_from_point_and_time', + side_effect=[ + # Azimuth time + ['reference_slc_id'], + # using two "dummy" ids to mimic GUNW sec granules + # See docstring + ['secondary_slc_id', 'secondary_slc_id'], + ], + ) mocker.patch( 'RAiDER.s1_azimuth_timing.get_orbits_from_slc_ids', side_effect=[ # For azimuth time [str(orbit_dict_for_azimuth_time_test['reference'])], - [str(orbit_dict_for_azimuth_time_test['secondary']), str(orbit_dict_for_azimuth_time_test['secondary'])], - ] + [ + str(orbit_dict_for_azimuth_time_test['secondary']), + str(orbit_dict_for_azimuth_time_test['secondary']), + ], + ], ) - weather_model_path_dict = (weather_model_dict_for_center_time_test - if interp_method == 'center_time' - else weather_model_dict_for_azimuth_time_test) + weather_model_path_dict = ( + weather_model_dict_for_center_time_test + if interp_method == 'center_time' + else weather_model_dict_for_azimuth_time_test + ) side_effect = weather_model_path_dict[weather_model_name] # RAiDER needs strings for paths side_effect = list(map(str, side_effect)) - mocker.patch('RAiDER.processWM.prepareWeatherModel', - side_effect=side_effect) + mocker.patch('RAiDER.processWM.prepareWeatherModel', side_effect=side_effect) + # fmt: off iargs = [ - '--file', str(out_path), - '--weather-model', weather_model_name, - '-interp', interp_method - ] + '--file', str(out_path), + '--weather-model', weather_model_name, + '-interp', interp_method, + '--output-directory', str(tmp_path), + ] + # fmt: on calcDelaysGUNW(iargs) # Check metadata - model_times_dict = {'reference': ["20210723T01:00:00", "20210723T02:00:00"], - 'secondary': ["20210711T01:00:00", "20210711T02:00:00"]} - time_dict = {'reference': "20210723T01:50:24", - 'secondary': "20210711T01:50:24"} + model_times_dict = { + 'reference': ['20210723T01:00:00', '20210723T02:00:00'], + 'secondary': ['20210711T01:00:00', '20210711T02:00:00'], + } + time_dict = {'reference': '20210723T01:50:24', 'secondary': '20210711T01:50:24'} for insar_date in ['reference', 'secondary']: group = f'science/grids/corrections/external/troposphere/HRRR/{insar_date}' with xr.open_dataset(out_path, group=group) as ds: @@ -478,58 +541,67 @@ def test_provenance_metadata_for_tropo_group(weather_model_name: str, assert ds[var].attrs['model_times_used'] == model_times_used -def test_hrrr_availability_check_using_gunw_ids(mocker): - """Hits the HRRR servers and makes sure that for certain dates they are indeed flagged as false - """ - +def test_hrrr_availability_check_using_gunw_ids() -> None: + """Hits the HRRR servers and makes sure that for certain dates they are indeed flagged as false.""" # All dates in 2023 are available gunw_id = 'S1-GUNW-A-R-106-tops-20230108_20230101-225947-00078W_00041N-PP-4be8-v3_0_0' - assert check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id) + assert check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id) # 2016-08-09 16:00:00 is a missing date gunw_id = 'S1-GUNW-A-R-106-tops-20160809_20140101-160001-00078W_00041N-PP-4be8-v3_0_0' - assert not check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id) + assert not check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id) -def test_hyp3_exits_succesfully_when_hrrr_not_available(mocker): +def test_hyp3_exits_succesfully_when_hrrr_not_available(mocker: MockerFixture) -> None: """This test performs the GUNW entrypoint with bucket/prefix provided and only updates the json. Monkey patches the upload/download to/from s3 and the actual computation. """ # 2016-08-09 16:00:00 is a missing date - mocker.patch('RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation', - side_effect=[False]) + mocker.patch( + 'RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation', side_effect=[False] + ) # The gunw id should not have a hyp3 file associated with it # This call will still hit the HRRR s3 API as done in the previous test - mocker.patch("RAiDER.aws.get_s3_file", side_effect=[Path('hyp3-job-uuid-3ad24/S1-GUNW-A-R-106-tops-20160809_20140101-160001-00078W_00041N-PP-4be8-v3_0_0.nc')]) + mocker.patch( + 'RAiDER.aws.get_s3_file', + side_effect=[ + Path('hyp3-job-uuid-3ad24/S1-GUNW-A-R-106-tops-20160809_20140101-160001-00078W_00041N-PP-4be8-v3_0_0.nc') + ], + ) mocker.patch('RAiDER.aria.prepFromGUNW.check_weather_model_availability') + # fmt: off iargs = [ - '--bucket', 's3://foo', - '--bucket-prefix', 'hyp3-job-uuid-3ad24', - '--weather-model', 'HRRR', - '-interp', 'azimuth_time_grid' - ] + '--bucket', 's3://foo', + '--bucket-prefix', 'hyp3-job-uuid-3ad24', + '--weather-model', 'HRRR', + '-interp', 'azimuth_time_grid', + ] + # fmt: on out = calcDelaysGUNW(iargs) assert out is None # Ensure calcDelaysGUNW in raider.py ended after it saw HRRR was not available RAiDER.aria.prepFromGUNW.check_weather_model_availability.assert_not_called() -def test_GUNW_workflow_fails_if_a_download_fails(gunw_azimuth_test, orbit_dict_for_azimuth_time_test, mocker): +def test_GUNW_workflow_fails_if_a_download_fails(tmp_path: Path, gunw_azimuth_test, orbit_dict_for_azimuth_time_test, mocker: MockerFixture) -> None: """Makes sure for azimuth-time-grid interpolation that an error is raised if one of the files fails to - download and does not do additional processing""" + download and does not do additional processing. + """ # The first part is the same mock up as done in test_azimuth_timing_interp_against_center_time_interp # Maybe better mocks could be done - but this is sufficient or simply a factory for this test given # This is reused so many times. # These outputs are not needed since the orbits are specified above - mocker.patch('RAiDER.s1_azimuth_timing.get_slc_id_from_point_and_time', - side_effect=[ - # Azimuth time - ['reference_slc_id'], - # using two "dummy" ids to mimic GUNW sec granules - # See docstring - ['secondary_slc_id', 'secondary_slc_id'], - ]) + mocker.patch( + 'RAiDER.s1_azimuth_timing.get_slc_id_from_point_and_time', + side_effect=[ + # Azimuth time + ['reference_slc_id'], + # using two "dummy" ids to mimic GUNW sec granules + # See docstring + ['secondary_slc_id', 'secondary_slc_id'], + ], + ) mocker.patch( 'RAiDER.s1_azimuth_timing.get_orbits_from_slc_ids', @@ -537,7 +609,7 @@ def test_GUNW_workflow_fails_if_a_download_fails(gunw_azimuth_test, orbit_dict_f # For azimuth time [str(orbit_dict_for_azimuth_time_test['reference'])], [str(orbit_dict_for_azimuth_time_test['secondary']), str(orbit_dict_for_azimuth_time_test['secondary'])], - ] + ], ) # These are the important parts of this test @@ -545,160 +617,182 @@ def test_GUNW_workflow_fails_if_a_download_fails(gunw_azimuth_test, orbit_dict_f # There are two weather model files required for this particular mock up. First, one fails. mocker.patch('RAiDER.processWM.prepareWeatherModel', side_effect=[RuntimeError, 'weather_model.nc']) mocker.patch('RAiDER.s1_azimuth_timing.get_s1_azimuth_time_grid') + # fmt: off iargs_1 = [ - '--file', str(gunw_azimuth_test), - '--weather-model', 'HRRR', - '-interp', 'azimuth_time_grid' - ] + '--file', str(gunw_azimuth_test), + '--weather-model', 'HRRR', + '-interp', 'azimuth_time_grid', + '--output-directory', str(tmp_path), + ] + # fmt: on with pytest.raises(WrongNumberOfFiles): calcDelaysGUNW(iargs_1) RAiDER.s1_azimuth_timing.get_s1_azimuth_time_grid.assert_not_called() -def test_value_error_for_file_inputs_when_no_data_available(mocker): - """See test_hyp3_exits_succesfully_when_hrrr_not_available above +def test_value_error_for_file_inputs_when_no_data_available(tmp_path: Path, mocker: MockerFixture) -> None: + """See test_hyp3_exits_succesfully_when_hrrr_not_available above. In this case if a bucket is specified rather than a file; the program exits successfully! """ - mocker.patch('RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation', - side_effect=[False]) + mocker.patch( + 'RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation', side_effect=[False] + ) mocker.patch('RAiDER.aria.prepFromGUNW.main') + # fmt: off iargs = [ - '--file', 'foo.nc', - '--weather-model', 'HRRR', - '-interp', 'azimuth_time_grid' - ] + '--file', 'foo.nc', + '--weather-model', 'HRRR', + '-interp', 'azimuth_time_grid', + '--output-directory', str(tmp_path), + ] + # fmt: on with pytest.raises(NoWeatherModelData): calcDelaysGUNW(iargs) RAiDER.aria.prepFromGUNW.main.assert_not_called() -def test_get_acq_time_reference(): - """Tests if function extracts acquisition time for reference""" - gunw_id = "S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0" - expected_time = datetime(2022, 1, 15, 22, 59, 47) - result = _get_acq_time_from_gunw_id(gunw_id, "reference") - assert result == expected_time - -def test_get_acq_time_secondary(): - """Tests if function extracts acquisition time for secondary""" - gunw_id = "S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0" - expected_time = datetime(2021, 12, 22, 22, 59, 47) - result = _get_acq_time_from_gunw_id(gunw_id, "secondary") - assert result == expected_time - -def test_invalid_reference_or_secondary(): - """Tests if function raises error for invalid reference_or_secondary value""" - gunw_id = "S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0" - with pytest.raises(ValueError): - _get_acq_time_from_gunw_id(gunw_id, "invalid") - - -def test_check_hrrr_availability_all_true(): - """Tests if check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation returns True - when all check_hrrr_dataset_availability return True""" - - gunw_id = "S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0" - +def test_get_acq_time_reference() -> None: + """Tests if function extracts acquisition time for reference.""" + gunw_id = 'S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0' + expected_time = datetime(2022, 1, 15, 22, 59, 47) + result = _get_acq_time_from_gunw_id(gunw_id, 'reference') + assert result == expected_time + + +def test_get_acq_time_secondary() -> None: + """Tests if function extracts acquisition time for secondary.""" + gunw_id = 'S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0' + expected_time = datetime(2021, 12, 22, 22, 59, 47) + result = _get_acq_time_from_gunw_id(gunw_id, 'secondary') + assert result == expected_time + + +def test_invalid_reference_or_secondary() -> None: + """Tests if function raises error for invalid reference_or_secondary value.""" + gunw_id = 'S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0' + with pytest.raises(ValueError): + _get_acq_time_from_gunw_id(gunw_id, 'invalid') + + +def test_check_hrrr_availability_all_true() -> None: + """Tests if check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation returns True + when all check_hrrr_dataset_availability return True. + """ + gunw_id = 'S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0' + # Mock _get_acq_time_from_gunw_id to return expected times - assert check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id) + assert check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id) + -def test_get_slc_ids_from_gunw(): - test_path = Path('test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc') - assert get_slc_ids_from_gunw(test_path, 'reference') == 'S1A_IW_SLC__1SDV_20230320T180251_20230320T180309_047731_05BBDB_DCA0.zip' - assert get_slc_ids_from_gunw(test_path, 'secondary') == 'S1A_IW_SLC__1SDV_20220418T180246_20220418T180305_042831_051CC3_3C47.zip' +def test_get_slc_ids_from_gunw() -> None: + test_path = Path( + 'test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc' + ) + assert ( + get_slc_ids_from_gunw(test_path, 'reference') + == 'S1A_IW_SLC__1SDV_20230320T180251_20230320T180309_047731_05BBDB_DCA0.zip' + ) + assert ( + get_slc_ids_from_gunw(test_path, 'secondary') + == 'S1A_IW_SLC__1SDV_20220418T180246_20220418T180305_042831_051CC3_3C47.zip' + ) with pytest.raises(FileNotFoundError): get_slc_ids_from_gunw(Path('dummy.nc')) - + with pytest.raises(ValueError): get_slc_ids_from_gunw(test_path, 'tertiary') - + with pytest.raises(OSError): get_slc_ids_from_gunw(Path('test/weather_files/ERA-5_2020_01_30_T13_52_45_32N_35N_120W_115W.nc')) -def test_get_acq_time_valid_slc_id(): - """Tests if function extracts acquisition time for a valid slc_id""" - slc_id = "S1B_OPER_AUX_POEORB_OPOD_20210731T111940_V20210710T225942_20210712T005942.EOF" - expected_time = pd.Timestamp("20210731T111940") - result = get_acq_time_from_slc_id(slc_id) - assert result == expected_time +def test_get_acq_time_valid_slc_id() -> None: + """Tests if function extracts acquisition time for a valid slc_id.""" + slc_id = 'S1B_OPER_AUX_POEORB_OPOD_20210731T111940_V20210710T225942_20210712T005942.EOF' + expected_time = pd.Timestamp('20210731T111940') + result = get_acq_time_from_slc_id(slc_id) + assert result == expected_time -def test_get_acq_time_invalid_slc_id(): - """Tests if function raises error for an invalid slc_id format""" - invalid_slc_id = "test/gunw_azimuth_test_data/S1B_OPER_AUX_POEORB_OPOD_20210731T111940_V20210710T225942_20210712T005942.EOF" - with pytest.raises(ValueError): - get_acq_time_from_slc_id(invalid_slc_id) +def test_get_acq_time_invalid_slc_id() -> None: + """Tests if function raises error for an invalid slc_id format.""" + invalid_slc_id = ( + 'test/gunw_azimuth_test_data/S1B_OPER_AUX_POEORB_OPOD_20210731T111940_V20210710T225942_20210712T005942.EOF' + ) + with pytest.raises(ValueError): + get_acq_time_from_slc_id(invalid_slc_id) -def test_identify_which_hrrr_1(): - """Tests if function identifies the correct HRRR file""" - gunw_id = Path("test/gunw_azimuth_test_data/S1-GUNW-A-R-064-tops-20210723_20210711-015000-00119W_00033N-PP-6267-v2_0_6.nc") +def test_identify_which_hrrr_1() -> None: + """Tests if function identifies the correct HRRR file.""" + gunw_id = Path( + 'test/gunw_azimuth_test_data/S1-GUNW-A-R-064-tops-20210723_20210711-015000-00119W_00033N-PP-6267-v2_0_6.nc' + ) result = identify_which_hrrr(gunw_id) - assert result == "HRRR" + assert result == 'HRRR' -def test_identify_which_hrrr_2(): - """Tests if function identifies the correct HRRR file""" - gunw_id = Path("test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc") +def test_identify_which_hrrr_2() -> None: + """Tests if function identifies the correct HRRR file.""" + gunw_id = Path('test/gunw_test_data/S1-GUNW-D-R-059-tops-20230320_20220418-180300-00179W_00051N-PP-c92e-v2_0_6.nc') result = identify_which_hrrr(gunw_id) - assert result == "HRRRAK" + assert result == 'HRRRAK' -def test_cast_to_hrrrak_1(): - """Tests if function casts the HRRR file to HRRRAK""" - ak_bounds = [51.0, 71.0, -175., -130.0] - conus_bounds = [34.0,35.0, -91, -90.0] +def test_cast_to_hrrrak_1() -> None: + """Tests if function casts the HRRR file to HRRRAK.""" + ak_bounds = [51.0, 71.0, -175.0, -130.0] + conus_bounds = [34.0, 35.0, -91, -90.0] model = HRRR() model.checkValidBounds(conus_bounds) model.checkValidBounds(ak_bounds) - assert model._Name == "HRRR-AK" + assert model._Name == 'HRRR-AK' -def test_cast_to_hrrrak_2(): - """Tests if function casts the HRRR file to HRRRAK""" - ak_bounds = [51.0, 71.0, -175., -130.0] +def test_cast_to_hrrrak_2() -> None: + """Tests if function casts the HRRR file to HRRRAK.""" + ak_bounds = [51.0, 71.0, -175.0, -130.0] model = HRRRAK() model.checkValidBounds(ak_bounds) - assert model._Name == "HRRR-AK" + assert model._Name == 'HRRR-AK' -def test_cast_to_hrrrak_2b(): - """Tests if function casts the HRRR file to HRRRAK""" - ak_bounds = [60.0, 65.0, -150., -120.0] +def test_cast_to_hrrrak_2b() -> None: + """Tests if function casts the HRRR file to HRRRAK.""" + ak_bounds = [60.0, 65.0, -150.0, -120.0] model = HRRRAK() model.checkValidBounds(ak_bounds) - assert model._Name == "HRRR-AK" + assert model._Name == 'HRRR-AK' -def test_cast_to_hrrrak_3(): - """Tests if function casts the HRRR file to HRRRAK""" - conus_bounds = [34.0,35.0, -91, -90.0] +def test_cast_to_hrrrak_3() -> None: + """Tests if function casts the HRRR file to HRRRAK.""" + conus_bounds = [34.0, 35.0, -91, -90.0] model = HRRR() model.checkValidBounds(conus_bounds) - assert model._Name == "HRRR" + assert model._Name == 'HRRR' -def test_cast_to_hrrrak_4(): - """Tests if function casts the HRRR file to HRRRAK""" +def test_cast_to_hrrrak_4() -> None: + """Tests if function casts the HRRR file to HRRRAK.""" europe_bounds = [-1, 1, -1, 1] model = HRRR() with pytest.raises(ValueError): model.checkValidBounds(europe_bounds) -def test_identify_which_hrrr_invalid(): - """Tests if function raises error for an invalid gunw_id format""" - invalid_gunw_id = "dummy.nc" +def test_identify_which_hrrr_invalid() -> None: + """Tests if function raises error for an invalid gunw_id format.""" + invalid_gunw_id = 'dummy.nc' with pytest.raises(NoWeatherModelData): identify_which_hrrr(invalid_gunw_id) -def test_check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation_again(): - """Tests if function raises error for an invalid gunw_id format""" - gunw_id = "S1-GUNW-D-R-044-tops-20240418_20240406-171649-00163W_00069N-PP-af6b-v3_0_1.nc" - assert check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id, 'hrrrak') is True \ No newline at end of file +def test_check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation_again() -> None: + """Tests if function raises error for an invalid gunw_id format.""" + gunw_id = 'S1-GUNW-D-R-044-tops-20240418_20240406-171649-00163W_00069N-PP-af6b-v3_0_1.nc' + assert check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id, 'hrrrak') is True \ No newline at end of file diff --git a/test/test_HRRR_ztd.py b/test/test_HRRR_ztd.py index cd7b38674..72387eb99 100644 --- a/test/test_HRRR_ztd.py +++ b/test/test_HRRR_ztd.py @@ -1,22 +1,35 @@ -from test import TEST_DIR, pushd +from pathlib import Path import numpy as np import xarray as xr +from pytest_mock import MockerFixture + from RAiDER.cli.raider import calcDelays +from RAiDER.utilFcns import write_yaml +from test import WM_DIR + + +def test_hrrr_ztd(tmp_path: Path, data_for_hrrr_ztd: Path, mocker: MockerFixture) -> None: + mocker.patch('RAiDER.processWM.prepareWeatherModel', side_effect=[str(data_for_hrrr_ztd)]) + dct_group = { + 'aoi_group': {'bounding_box': [36, 37, -92, -91]}, + 'date_group': {'date_start': 20200101}, + 'time_group': {'time': '12:00:00', 'interpolate_time': 'none'}, + 'weather_model': 'HRRR', + 'height_group': {'height_levels': [0, 50, 100, 500, 1000]}, + 'runtime_group': { + 'output_directory': tmp_path, + 'weather_model_directory': WM_DIR, + }, + } -def test_hrrr_ztd(tmp_path, data_for_hrrr_ztd, mocker) -> None: - SCENARIO_DIR = TEST_DIR / 'scenario_1' - test_path = SCENARIO_DIR / 'raider_example_1.yaml' - mocker.patch('RAiDER.processWM.prepareWeatherModel', - side_effect=[str(data_for_hrrr_ztd)]) - - with pushd(tmp_path): - calcDelays([str(test_path)]) - new_data = xr.load_dataset('HRRR_tropo_20200101T120000_ztd.nc') + cfg = write_yaml(dct_group, tmp_path / 'temp.yaml') + calcDelays([str(cfg)]) + new_data = xr.load_dataset(tmp_path / 'HRRR_tropo_20200101T120000_ztd.nc') new_data1 = new_data.sel(x=-91.84, y=36.84, z=0, method='nearest') - golden_data = 2.2622863, 0.0361021 # hydro|wet + golden_data = 2.2622863, 0.0361021 # hydro|wet - np.testing.assert_almost_equal(golden_data[0], new_data1["hydro"].data) - np.testing.assert_almost_equal(golden_data[1], new_data1["wet"].data) + np.testing.assert_almost_equal(golden_data[0], new_data1['hydro'].data) + np.testing.assert_almost_equal(golden_data[1], new_data1['wet'].data) diff --git a/test/test_checkArgs.py b/test/test_checkArgs.py index cf2f031c4..dd154381f 100644 --- a/test/test_checkArgs.py +++ b/test/test_checkArgs.py @@ -1,6 +1,4 @@ import datetime -import os -import shutil from pathlib import Path import pandas as pd @@ -9,30 +7,32 @@ from RAiDER.checkArgs import checkArgs, get_raster_ext, makeDelayFileNames from RAiDER.cli.types import AOIGroup, DateGroup, HeightGroupUnparsed, LOSGroup, RunConfig, RuntimeGroup, TimeGroup from RAiDER.llreader import BoundingBox, RasterRDR, StationFile -from RAiDER.losreader import Zenith +from RAiDER.losreader import Conventional, Zenith from RAiDER.models.gmao import GMAO -from test import TEST_DIR, pushd +from test import TEST_DIR -SCENARIO_1 = os.path.join(TEST_DIR, 'scenario_1') -SCENARIO_2 = os.path.join(TEST_DIR, 'scenario_2') +SCENARIO_1 = TEST_DIR / 'scenario_1' +SCENARIO_2 = TEST_DIR / 'scenario_2' -@pytest.fixture(autouse=True) -def args(): - d = RunConfig( +@pytest.fixture() +def args_default_out_dir() -> RunConfig: + return RunConfig( weather_model=GMAO(), date_group=DateGroup(date_list=[datetime.datetime(2018, 1, 1)]), time_group=TimeGroup(time=datetime.time(12, 0, 0)), - aoi_group=AOIGroup(aoi=BoundingBox([38, 39, -92, -91])), + aoi_group=AOIGroup(aoi=BoundingBox((38, 39, -92, -91))), los_group=LOSGroup(los=Zenith()), height_group=HeightGroupUnparsed(), runtime_group=RuntimeGroup(), ) - for f in 'weather_files weather_dir'.split(): - shutil.rmtree(f) if os.path.exists(f) else '' - return d + +@pytest.fixture() +def args(tmp_path: Path, args_default_out_dir: RunConfig) -> RunConfig: + args_default_out_dir.runtime_group = RuntimeGroup(output_directory=tmp_path) + return args_default_out_dir def isWriteable(dirpath: Path) -> bool: @@ -41,93 +41,90 @@ def isWriteable(dirpath: Path) -> bool: with (dirpath / 'tmp.txt').open('w'): pass return True - except IOError: + except OSError: return False -def test_checkArgs_outfmt_1(args): +def test_checkArgs_outfmt_1(args: RunConfig) -> None: args.runtime_group.file_format = 'h5' args.height_group.height_levels = [10, 100, 1000] args = checkArgs(args) - assert os.path.splitext(args.wetFilenames[0])[-1] == '.h5' + assert Path(args.wetFilenames[0]).suffix == '.h5' -def test_checkArgs_outfmt_2(args): +def test_checkArgs_outfmt_2(args: RunConfig) -> None: args.runtime_group.file_format = 'GTiff' args.height_group.height_levels = [10, 100, 1000] args = checkArgs(args) - assert os.path.splitext(args.wetFilenames[0])[-1] == '.nc' + assert Path(args.wetFilenames[0]).suffix == '.nc' -def test_checkArgs_outfmt_3(args): +def test_checkArgs_outfmt_3(args: RunConfig) -> None: with pytest.raises(FileNotFoundError): - args.aoi_group.aoi = StationFile(os.path.join('fake_dir', 'stations.csv')) + args.aoi_group.aoi = StationFile(Path('fake_dir/stations.csv')) -def test_checkArgs_outfmt_4(args): +def test_checkArgs_outfmt_4(args: RunConfig) -> None: args.aoi_group.aoi = RasterRDR( - lat_file=os.path.join(SCENARIO_1, 'geom', 'lat.dat'), - lon_file=os.path.join(SCENARIO_1, 'geom', 'lon.dat'), + lat_file=SCENARIO_1 / 'geom/lat.dat', + lon_file=SCENARIO_1 / 'geom/lon.dat', ) args = checkArgs(args) assert args.aoi_group.aoi.type() == 'radar_rasters' -def test_checkArgs_outfmt_5(args, tmp_path): - with pushd(tmp_path): - args.aoi_group.aoi = StationFile(os.path.join(SCENARIO_2, 'stations.csv')) - args = checkArgs(args) - assert pd.read_csv(args.wetFilenames[0]).shape == (8, 4) +def test_checkArgs_outfmt_5(args: RunConfig) -> None: + args.aoi_group.aoi = StationFile(SCENARIO_2 / 'stations.csv') + args = checkArgs(args) + assert pd.read_csv(args.wetFilenames[0]).shape == (8, 4) -def test_checkArgs_outloc_1(args): +def test_checkArgs_outloc_1(args_default_out_dir: RunConfig) -> None: """Test that the default output and weather model directories are correct.""" - args = args - argDict = checkArgs(args) - out = argDict.runtime_group.output_directory - wmLoc = argDict.runtime_group.weather_model_directory - assert os.path.abspath(out) == os.getcwd() - assert os.path.abspath(wmLoc) == os.path.join(os.getcwd(), 'weather_files') + cwd = Path.cwd().resolve() + expected_wm_dir = (cwd / 'weather_files') + assert not expected_wm_dir.exists(), ( + 'weather_files/ already exists; cannot ensure weather RAiDER will create it. ' + 'Please remove this directory and run the test again.' + ) + run_config = checkArgs(args_default_out_dir) + actual_out_dir = run_config.runtime_group.output_directory + actual_wm_dir = run_config.runtime_group.weather_model_directory + actual_wm_dir.rmdir() + assert actual_out_dir.resolve() == cwd + assert actual_wm_dir.resolve() == expected_wm_dir -def test_checkArgs_outloc_2(args, tmp_path): +def test_checkArgs_outloc_2(tmp_path: Path, args_default_out_dir: RunConfig) -> None: """Tests that the correct output location gets assigned when provided.""" - with pushd(tmp_path): - args.runtime_group.output_directory = tmp_path - argDict = checkArgs(args) - out = argDict.runtime_group.output_directory - assert out == tmp_path + args_default_out_dir.runtime_group = RuntimeGroup(output_directory=tmp_path) + argDict = checkArgs(args_default_out_dir) + out = argDict.runtime_group.output_directory + assert out.resolve() == tmp_path.resolve() -def test_checkArgs_outloc_2b(args, tmp_path): +def test_checkArgs_outloc_2b(tmp_path: Path, args: RunConfig) -> None: """Tests that the weather model directory gets passed through by itself.""" - with pushd(tmp_path): - args.runtime_group.output_directory = tmp_path - wm_dir = Path('weather_dir') - args.runtime_group.weather_model_directory = wm_dir - argDict = checkArgs(args) - assert argDict.runtime_group.weather_model_directory == wm_dir + wm_dir = tmp_path / 'weather_dir' + args.runtime_group.weather_model_directory = wm_dir + argDict = checkArgs(args) + assert argDict.runtime_group.weather_model_directory == wm_dir -def test_checkArgs_outloc_3(args, tmp_path): +def test_checkArgs_outloc_3(args: RunConfig) -> None: """Tests that the weather model directory gets created when needed.""" - with pushd(tmp_path): - args.runtime_group.output_directory = tmp_path - argDict = checkArgs(args) - assert argDict.runtime_group.weather_model_directory.is_dir() + argDict = checkArgs(args) + assert argDict.runtime_group.weather_model_directory.is_dir() -def test_checkArgs_outloc_4(args): +def test_checkArgs_outloc_4(args: RunConfig) -> None: """Tests for creating writeable weather model directory.""" - args = args argDict = checkArgs(args) - assert isWriteable(argDict.runtime_group.weather_model_directory) -def test_filenames_1(args): +def test_filenames_1(args: RunConfig) -> None: """tests that the correct filenames are generated.""" - args = args argDict = checkArgs(args) assert 'Delay' not in argDict.wetFilenames[0] assert 'wet' in argDict.wetFilenames[0] @@ -137,37 +134,41 @@ def test_filenames_1(args): assert len(argDict.hydroFilenames) == 1 -def test_filenames_2(args): +def test_filenames_2(args: RunConfig) -> None: """Tests that the correct filenames are generated.""" - args.runtime_group.output_directory = Path(SCENARIO_2) - args.aoi_group.aoi = StationFile(os.path.join(SCENARIO_2, 'stations.csv')) + args.aoi_group.aoi = StationFile(SCENARIO_2 / 'stations.csv') argDict = checkArgs(args) assert '20180101' in argDict.wetFilenames[0] assert len(argDict.wetFilenames) == 1 -def test_makeDelayFileNames_1(): +def test_makeDelayFileNames_1() -> None: assert makeDelayFileNames(None, None, 'h5', 'name', Path('dir')) == ('dir/name_wet_ztd.h5', 'dir/name_hydro_ztd.h5') -def test_makeDelayFileNames_2(): - assert makeDelayFileNames(None, (), 'h5', 'name', Path('dir')) == ('dir/name_wet_std.h5', 'dir/name_hydro_std.h5') +def test_makeDelayFileNames_2() -> None: + assert makeDelayFileNames(None, Conventional(), 'h5', 'name', Path('dir')) == ( + 'dir/name_wet_std.h5', + 'dir/name_hydro_std.h5', + ) -def test_makeDelayFileNames_3(): +def test_makeDelayFileNames_3() -> None: assert makeDelayFileNames(datetime.datetime(2020, 1, 1, 1, 2, 3), None, 'h5', 'model_name', Path('dir')) == ( 'dir/model_name_wet_20200101T010203_ztd.h5', 'dir/model_name_hydro_20200101T010203_ztd.h5', ) -def test_makeDelayFileNames_4(): - assert makeDelayFileNames(datetime.datetime(1900, 12, 31, 1, 2, 3), 'los', 'h5', 'model_name', Path('dir')) == ( +def test_makeDelayFileNames_4() -> None: + assert makeDelayFileNames( + datetime.datetime(1900, 12, 31, 1, 2, 3), Conventional(), 'h5', 'model_name', Path('dir') + ) == ( 'dir/model_name_wet_19001231T010203_std.h5', 'dir/model_name_hydro_19001231T010203_std.h5', ) -def test_get_raster_ext(): +def test_get_raster_ext() -> None: with pytest.raises(ValueError): get_raster_ext('dummy_format') diff --git a/test/test_datelist.py b/test/test_datelist.py index ebf7a3096..9198c477a 100644 --- a/test/test_datelist.py +++ b/test/test_datelist.py @@ -1,18 +1,12 @@ import datetime -import os -import shutil +from pathlib import Path -from RAiDER.utilFcns import write_yaml -from test import TEST_DIR, WM, pushd from RAiDER.cli.raider import read_run_config_file +from RAiDER.utilFcns import write_yaml +from test import WM, WM_DIR -def test_datelist(tmp_path): - SCENARIO_DIR = os.path.join(TEST_DIR, "datelist") - if os.path.exists(SCENARIO_DIR): - shutil.rmtree(SCENARIO_DIR) - os.makedirs(SCENARIO_DIR, exist_ok=False) - +def test_datelist(tmp_path: Path): dates = ['20200124', '20200130'] true_dates = [ datetime.date(2020, 1, 24), @@ -25,19 +19,17 @@ def test_datelist(tmp_path): 'time_group': {'time': '00:00:00', 'interpolate_time': 'none'}, 'weather_model': WM, 'runtime_group': { - 'output_directory': SCENARIO_DIR, - 'weather_model_directory': os.path.join(SCENARIO_DIR, 'weather_files') - } - } - - with pushd(tmp_path): - cfg = write_yaml(dct_group, 'temp.yaml') - param_dict = read_run_config_file(cfg) - assert param_dict.date_group.date_list == true_dates - - -def test_datestep(tmp_path): - SCENARIO_DIR = os.path.join(TEST_DIR, "scenario_5") + 'output_directory': tmp_path, + 'weather_model_directory': WM_DIR + } + } + + cfg = write_yaml(dct_group, tmp_path / 'temp.yaml') + param_dict = read_run_config_file(cfg) + assert param_dict.date_group.date_list == true_dates + + +def test_datestep(tmp_path: Path): st, en, step = "20200124", "20200130", 3 true_dates = [ datetime.date(2020, 1, 24), @@ -51,12 +43,11 @@ def test_datestep(tmp_path): 'time_group': {'time': '00:00:00', 'interpolate_time': 'none'}, 'weather_model': WM, 'runtime_group': { - 'output_directory': SCENARIO_DIR, - 'weather_model_directory': os.path.join(SCENARIO_DIR, 'weather_files') - } - } - - with pushd(tmp_path): - cfg = write_yaml(dct_group, 'temp.yaml') - param_dict = read_run_config_file(cfg) - assert param_dict.date_group.date_list == true_dates + 'output_directory': tmp_path, + 'weather_model_directory': WM_DIR + } + } + + cfg = write_yaml(dct_group, tmp_path / 'temp.yaml') + param_dict = read_run_config_file(cfg) + assert param_dict.date_group.date_list == true_dates diff --git a/test/test_delayFcns.py b/test/test_delayFcns.py index 2353f5d56..bf9dec466 100644 --- a/test/test_delayFcns.py +++ b/test/test_delayFcns.py @@ -1,16 +1,14 @@ -import os -import pytest - import numpy as np +import pytest import xarray as xr - from pyproj import CRS, Transformer -from test import TEST_DIR -from RAiDER.delayFcns import getInterpolators from RAiDER.delay import transformPoints +from RAiDER.delayFcns import getInterpolators +from test import TEST_DIR + -SCENARIO1_DIR = os.path.join(TEST_DIR, "scenario_1", "golden_data") +SCENARIO1_DIR = TEST_DIR / "scenario_1/golden_data" @pytest.fixture @@ -28,8 +26,8 @@ def hrrr_proj(): @pytest.fixture -def wmdata(): - return xr.load_dataset(os.path.join(SCENARIO1_DIR, 'HRRR_tropo_20200101T120000_ztd.nc')) +def wmdata() -> xr.Dataset: + return xr.load_dataset(SCENARIO1_DIR / 'HRRR_tropo_20200101T120000_ztd.nc') def test_getInterpolators(wmdata): diff --git a/test/test_dem.py b/test/test_dem.py index c1575a737..0fe1b4c9f 100644 --- a/test/test_dem.py +++ b/test/test_dem.py @@ -1,39 +1,34 @@ +from pathlib import Path + import pytest -from test import TEST_DIR, pushd from RAiDER.dem import download_dem +from test import TEST_DIR -def test_download_dem_1(): - SCENARIO_1 = TEST_DIR / "scenario_4" - hts, meta = download_dem( - dem_path=SCENARIO_1 / 'warpedDEM.rdr', - overwrite=False - ) - assert hts.shape == (45,226) +def test_download_dem_1() -> None: + SCENARIO_1 = TEST_DIR / 'scenario_4' + hts, meta = download_dem(dem_path=SCENARIO_1 / 'warpedDEM.rdr', overwrite=False) + assert hts.shape == (45, 226) assert meta is not None assert meta['crs'] is None -def test_download_dem_2(): +def test_download_dem_2() -> None: with pytest.raises(ValueError): download_dem() -def test_download_dem_3(tmp_path): - with pushd(tmp_path): - path = tmp_path / 'tmp_file.nc' - with pytest.raises(ValueError): - download_dem(dem_path=path) +def test_download_dem_3(tmp_path: Path) -> None: + path = tmp_path / 'tmp_file.nc' + with pytest.raises(ValueError): + download_dem(dem_path=path) @pytest.mark.long -def test_download_dem_4(tmp_path): - with pushd(tmp_path): - path = tmp_path / 'tmp_file.nc' - z, m = download_dem(dem_path=path, overwrite=True, ll_bounds=[37.9,38.,-91.8,-91.7], writeDEM=True) - assert len(z.shape) == 2 - assert m is not None - assert 'crs' in m.keys() - - +def test_download_dem_4(tmp_path: Path) -> None: + path = tmp_path / 'tmp_file.nc' + z, m = download_dem(dem_path=path, overwrite=True, ll_bounds=[37.9, 38.0, -91.8, -91.7], writeDEM=True) + assert len(z.shape) == 2 + assert m is not None + assert 'crs' in m.keys() diff --git a/test/test_downloadGNSS.py b/test/test_downloadGNSS.py index 8f418f52d..34e8059fd 100644 --- a/test/test_downloadGNSS.py +++ b/test/test_downloadGNSS.py @@ -1,18 +1,17 @@ +from unittest import mock + import pytest import requests -from unittest import mock from RAiDER.gnss.downloadGNSSDelays import ( check_url, - in_box, + download_UNR, fix_lons, get_ID, - download_UNR, + in_box, main, ) -from test import pushd - # Test check_url with a valid and invalid URL def test_check_url_valid(): @@ -84,9 +83,8 @@ def test_download_UNR(tmp_path): expected_path = "https://geodesy.unr.edu/gps_timeseries/trop/MORZ/MORZ.2020.trop.zip" statID = "MORZ" year = 2020 - with pushd(tmp_path): - outDict = download_UNR(statID, year) - assert outDict["path"] == expected_path + outDict = download_UNR(statID, year, tmp_path) + assert outDict["path"] == expected_path def test_download_UNR_2(): diff --git a/test/test_gnss.py b/test/test_gnss.py index 2499eeed2..5d95a70d2 100644 --- a/test/test_gnss.py +++ b/test/test_gnss.py @@ -8,10 +8,10 @@ from RAiDER.gnss.downloadGNSSDelays import download_tropo_delays, filterToBBox, get_station_list, get_stats_by_llh from RAiDER.gnss.processDelayFiles import addDateTimeToFiles, concatDelayFiles, getDateTime from RAiDER.models.customExceptions import NoStationDataFoundError -from test import TEST_DIR, pushd +from test import TEST_DIR -SCENARIO2_DIR = os.path.join(TEST_DIR, "scenario_2") +SCENARIO2_DIR = TEST_DIR / 'scenario_2' def file_len(path: Path) -> int: @@ -24,9 +24,9 @@ def temp_file(): df = pd.DataFrame( { 'ID': ['STAT1', 'STAT2', 'STAT3'], - 'Lat': [15.0, 20., 25.0], - 'Lon': [-100, -90., -85.], - 'totalDelay': [1., 1.5, 2.], + 'Lat': [15.0, 20.0, 25.0], + 'Lon': [-100, -90.0, -85.0], + 'totalDelay': [1.0, 1.5, 2.0], } ) return df @@ -51,48 +51,42 @@ def test_getDateTime(): def test_addDateTimeToFiles1(tmp_path, temp_file): df = temp_file - with pushd(tmp_path): - new_path = tmp_path / 'tmp.csv' - df.to_csv(new_path, index=False) - addDateTimeToFiles([new_path]) - df = pd.read_csv(new_path) - assert 'Datetime' not in df.columns + new_path = tmp_path / 'tmp.csv' + df.to_csv(new_path, index=False) + addDateTimeToFiles([new_path]) + df = pd.read_csv(new_path) + assert 'Datetime' not in df.columns def test_addDateTimeToFiles2(tmp_path, temp_file): f1 = '20080101T060000' df = temp_file - with pushd(tmp_path): - new_path = tmp_path / f'tmp{f1}.csv' - df.to_csv(new_path, index=False) - addDateTimeToFiles([new_path]) - df = pd.read_csv(new_path) - assert 'Datetime' in df.columns + new_path = tmp_path / f'tmp{f1}.csv' + df.to_csv(new_path, index=False) + addDateTimeToFiles([new_path]) + df = pd.read_csv(new_path) + assert 'Datetime' in df.columns def test_concatDelayFiles(tmp_path, temp_file): f1 = '20080101T060000' df = temp_file - with pushd(tmp_path): - new_path1 = tmp_path / f'tmp{f1}_1.csv' - new_path2 = tmp_path / f'tmp{f1}_2.csv' - df.to_csv(new_path1, index=False) - df.to_csv(new_path2, index=False) - file_length = file_len(new_path1) - addDateTimeToFiles([new_path1, new_path2]) - - out_path = tmp_path / 'out.csv' - concatDelayFiles( - [new_path1, new_path2], - outName=out_path - ) + new_path1 = tmp_path / f'tmp{f1}_1.csv' + new_path2 = tmp_path / f'tmp{f1}_2.csv' + df.to_csv(new_path1, index=False) + df.to_csv(new_path2, index=False) + file_length = file_len(new_path1) + addDateTimeToFiles([new_path1, new_path2]) + + out_path = tmp_path / 'out.csv' + concatDelayFiles([new_path1, new_path2], outName=out_path) assert file_len(out_path) == file_length def test_get_stats_by_llh2(): - stations = get_stats_by_llh(llhBox=[10, 18, 360-93, 360-88]) + stations = get_stats_by_llh(llhBox=[10, 18, 360 - 93, 360 - 88]) assert isinstance(stations, pd.DataFrame) @@ -102,16 +96,20 @@ def test_get_stats_by_llh3(): def test_get_station_list(): - stations, output_file = get_station_list(stationFile=os.path.join( - SCENARIO2_DIR, 'stations.csv'), writeStationFile=False) + stations, output_file = get_station_list( + stationFile=os.path.join(SCENARIO2_DIR, 'stations.csv'), writeStationFile=False + ) assert isinstance(stations, list) assert isinstance(output_file, pd.DataFrame) def test_download_tropo_delays1(): with pytest.raises(NotImplementedError): - download_tropo_delays(stats=['GUAT', 'SLAC', 'CRSE'], years=[ - 2022], gps_repo='dummy_repo') + download_tropo_delays( + stats=['GUAT', 'SLAC', 'CRSE'], + years=[2022], + gps_repo='dummy_repo', + ) def test_download_tropo_delays2(): @@ -119,35 +117,37 @@ def test_download_tropo_delays2(): download_tropo_delays(stats=['dummy_station'], years=[2022]) -def test_download_tropo_delays2(tmp_path): - with pushd(tmp_path): - stations, output_file = get_station_list( - stationFile=os.path.join(SCENARIO2_DIR, 'stations.csv') - ) +def test_download_tropo_delays3(tmp_path: Path): + stations, output_file = get_station_list( + stationFile=str(SCENARIO2_DIR / 'stations.csv'), + writeLoc=tmp_path, + ) - # spot check a couple of stations - assert 'CAPE' in stations - assert 'FGNW' in stations - assert isinstance(output_file, str) + # spot check a couple of stations + assert 'CAPE' in stations + assert 'FGNW' in stations + assert isinstance(output_file, str) - # try downloading the delays - download_tropo_delays(stats=stations, years=[2022], writeDir=tmp_path) - assert True + # try downloading the delays + download_tropo_delays(stats=stations, years=[2022], writeDir=str(tmp_path)) def test_filterByBBox1(): - _, station_data = get_station_list(stationFile=os.path.join( - SCENARIO2_DIR, 'stations.csv'), writeStationFile=False) + _, station_data = get_station_list( + stationFile=str(SCENARIO2_DIR / 'stations.csv'), + writeStationFile=False, + ) with pytest.raises(ValueError): filterToBBox(station_data, llhBox=[34, 38, -120, -115]) def test_filterByBBox2(): - _, station_data = get_station_list(stationFile=os.path.join( - SCENARIO2_DIR, 'stations.csv'), writeStationFile=False) + _, station_data = get_station_list( + stationFile=str(SCENARIO2_DIR / 'stations.csv'), + writeStationFile=False, + ) new_data = filterToBBox(station_data, llhBox=[34, 38, 240, 245]) for stat in ['CAPE', 'MHMS', 'NVCO']: assert stat not in new_data['ID'].to_list() for stat in ['FGNW', 'JPLT', 'NVTP', 'WLHG', 'WORG']: assert stat in new_data['ID'].to_list() - diff --git a/test/test_interpolator.py b/test/test_interpolator.py index de8851d72..abaa91e46 100644 --- a/test/test_interpolator.py +++ b/test/test_interpolator.py @@ -930,7 +930,7 @@ def f(x, y, z): assert np.allclose(ans2, ans_scipy, 1e-15, equal_nan=True) -def test_interpolateDEM(): +def test_interpolateDEM(tmp_path: Path): from affine import Affine s = 10 @@ -943,7 +943,7 @@ def test_interpolateDEM(): 'crs': rio.crs.CRS.from_epsg(4326), } - dem_file = Path('./dem_tmp.tif') + dem_file = tmp_path / 'dem_tmp.tif' with rio.open(dem_file, 'w', **metadata) as ds: ds.write(dem, 1) ds.update_tags(AREA_OR_POINT='Point') @@ -954,10 +954,9 @@ def test_interpolateDEM(): out = interpolateDEM(dem_file, (lats, lons)) gold = np.array([[4., 8.], [28., 56.]], dtype=float) assert np.allclose(out, gold) - dem_file.unlink() -def test_interpolateDEM_2(): +def test_interpolateDEM_2(tmp_path: Path): from affine import Affine s = 10 x = np.arange(s) @@ -969,7 +968,7 @@ def test_interpolateDEM_2(): 'crs': rio.crs.CRS.from_epsg(4326), } - dem_file = Path('./dem_tmp.tif') + dem_file = tmp_path / 'dem_tmp.tif' with rio.open(dem_file, 'w', **metadata) as ds: ds.write(dem, 1) ds.update_tags(AREA_OR_POINT='Point') diff --git a/test/test_intersect.py b/test/test_intersect.py index 1475ef823..87c1cdafe 100644 --- a/test/test_intersect.py +++ b/test/test_intersect.py @@ -81,7 +81,7 @@ def test_cube_intersect(tmp_path: Path, wm: str) -> None: ) def test_gnss_intersect(tmp_path: Path, wm_name: str, gold: np.float64) -> None: gnss_file = SCENARIO_DIR / 'stations.csv' - outdir = tmp_path / 'output' + out_dir = tmp_path / 'output' id = 'TORP' @@ -95,7 +95,7 @@ def test_gnss_intersect(tmp_path: Path, wm_name: str, gold: np.float64) -> None: 'weather_model': wm_name, 'aoi_group': {'station_file': str(gnss_file)}, 'runtime_group': { - 'output_directory': outdir, + 'output_directory': out_dir, 'weather_model_directory': WM_DIR, }, 'verbose': False, @@ -107,7 +107,7 @@ def test_gnss_intersect(tmp_path: Path, wm_name: str, gold: np.float64) -> None: ## run raider and intersect calcDelays([str(cfg)]) - df = pd.read_csv(outdir / f'{wm_name}_Delay_{date}T{time.replace(":", "")}_ztd.csv') + df = pd.read_csv(out_dir / f'{wm_name}_Delay_{date}T{time.replace(":", "")}_ztd.csv') td = df['totalDelay'][df['ID'] == id].values # test for equality with golden data diff --git a/test/test_llreader.py b/test/test_llreader.py index b22ad3a73..7b0bcdeb7 100644 --- a/test/test_llreader.py +++ b/test/test_llreader.py @@ -40,7 +40,7 @@ def test_latlon_reader_2(): RasterRDR(lat_file=None, lon_file=None) with pytest.raises(ValueError): - RasterRDR(lat_file='doesnotexist.rdr', lon_file='doesnotexist.rdr') + RasterRDR(lat_file=Path('doesnotexist.rdr'), lon_file=Path('doesnotexist.rdr')) def test_aoi_epsg(): @@ -54,8 +54,8 @@ def test_aoi_epsg(): def test_set_output_dir(): bbox = [20, 27, -115, -104] r = BoundingBox(bbox) - r.set_output_directory('dummy_directory') - assert r._output_directory == 'dummy_directory' + r.set_output_directory(Path('dummy_directory')) + assert r._output_directory == Path('dummy_directory') def test_set_xygrid(): @@ -74,7 +74,7 @@ def test_latlon_reader(): lat_true, _ = rio_open(latfile) lon_true, _ = rio_open(lonfile) - query = RasterRDR(lat_file=str(latfile), lon_file=str(lonfile)) + query = RasterRDR(lat_file=latfile, lon_file=lonfile) lats, lons = query.readLL() assert lats.shape == (45, 226) assert lons.shape == (45, 226) @@ -88,8 +88,8 @@ def test_latlon_reader(): def test_badllfiles(station_file): - latfile = os.path.join(GEOM_DIR, 'lat.rdr') - lonfile = os.path.join(GEOM_DIR, 'lon_dummy.rdr') + latfile = Path(GEOM_DIR) / 'lat.rdr' + lonfile = Path(GEOM_DIR) / 'lon_dummy.rdr' station_file = station_file with pytest.raises(ValueError): RasterRDR(lat_file=latfile, lon_file=lonfile) diff --git a/test/test_s1_orbits.py b/test/test_s1_orbits.py index 0803ae468..bf058a66b 100644 --- a/test/test_s1_orbits.py +++ b/test/test_s1_orbits.py @@ -5,7 +5,7 @@ def test_get_orbits_from_slc_ids(tmp_path): with patch('s1_orbits.fetch_for_scene', side_effect=['foo.eof', 'bar.eof', 'foo.eof']) as mock_fetch_for_scene: - assert get_orbits_from_slc_ids(['scene1', 'scene2', 'scene3'], str(tmp_path)) == ['bar.eof', 'foo.eof'] + assert get_orbits_from_slc_ids(['scene1', 'scene2', 'scene3'], tmp_path) == ['bar.eof', 'foo.eof'] mock_fetch_for_scene.assert_has_calls( [ call('scene1', tmp_path), @@ -17,6 +17,6 @@ def test_get_orbits_from_slc_ids(tmp_path): orbit_dir = tmp_path / 'orbits' assert not orbit_dir.exists() with patch('s1_orbits.fetch_for_scene', return_value='a.eof') as mock_fetch_for_scene: - assert get_orbits_from_slc_ids(['scene4'], str(orbit_dir)) == ['a.eof'] + assert get_orbits_from_slc_ids(['scene4'], orbit_dir) == ['a.eof'] mock_fetch_for_scene.assert_called_once_with('scene4', orbit_dir) assert orbit_dir.exists() diff --git a/test/test_scenario_4.py b/test/test_scenario_4.py index d52076926..1cd4e97dd 100644 --- a/test/test_scenario_4.py +++ b/test/test_scenario_4.py @@ -17,7 +17,7 @@ from RAiDER.models.merra2 import MERRA2 from RAiDER.models.weatherModel import WeatherModel from RAiDER.processWM import prepareWeatherModel -from test import TEST_DIR, pushd +from test import TEST_DIR SCENARIO_DIR = TEST_DIR / 'scenario_4' @@ -39,23 +39,24 @@ ), ) def test_aoi_without_xpts(tmp_path: Path, Model: type[WeatherModel]) -> None: - with pushd(tmp_path): - los = Zenith() - latfile = str(SCENARIO_DIR / 'lat.rdr') - lonfile = str(SCENARIO_DIR / 'lon.rdr') - hgtfile = str(SCENARIO_DIR / 'hgt.rdr') - aoi = RasterRDR(latfile, lonfile, hgt_file=hgtfile) - - wm = Model() - wm.set_latlon_bounds(aoi.bounds()) - wm.setTime(DATETIME) - wm_file_path = prepareWeatherModel(wm, DATETIME, aoi.bounds()) - zen_wet, zen_hydro = tropo_delay(DATETIME, wm_file_path, aoi, los) - - assert zen_wet.ndim == 2 - assert np.sum(np.isnan(zen_wet)) < zen_wet.size - assert np.nanmean(zen_wet) > 0 - assert np.nanmean(zen_hydro) > 0 + los = Zenith() + latfile = SCENARIO_DIR / 'lat.rdr' + lonfile = SCENARIO_DIR / 'lon.rdr' + hgtfile = SCENARIO_DIR / 'hgt.rdr' + aoi = RasterRDR(latfile, lonfile, hgt_file=hgtfile) + aoi.set_output_directory(tmp_path) + + wm = Model() + wm.set_latlon_bounds(aoi.bounds()) + wm.setTime(DATETIME) + wm.set_wmLoc(str(tmp_path)) + wm_file_path = prepareWeatherModel(wm, DATETIME, aoi.bounds()) + zen_wet, zen_hydro = tropo_delay(DATETIME, wm_file_path, aoi, los) + + assert zen_wet.ndim == 2 + assert np.sum(np.isnan(zen_wet)) < zen_wet.size + assert np.nanmean(zen_wet) > 0 + assert np.nanmean(zen_hydro) > 0 @pytest.mark.long @@ -71,25 +72,26 @@ def test_aoi_without_xpts(tmp_path: Path, Model: type[WeatherModel]) -> None: ), ) def test_get_delays_on_cube(tmp_path: Path, Model: type[WeatherModel]) -> None: - with pushd(tmp_path): - los = Zenith() - latfile = str(SCENARIO_DIR / 'lat.rdr') - lonfile = str(SCENARIO_DIR / 'lon.rdr') - hgtfile = str(SCENARIO_DIR / 'hgt.rdr') - aoi = RasterRDR(latfile, lonfile, hgt_file=hgtfile) + los = Zenith() + latfile = SCENARIO_DIR / 'lat.rdr' + lonfile = SCENARIO_DIR / 'lon.rdr' + hgtfile = SCENARIO_DIR / 'hgt.rdr' + aoi = RasterRDR(latfile, lonfile, hgt_file=hgtfile) + aoi.set_output_directory(tmp_path) - wm = Model() - wm.set_latlon_bounds(aoi.bounds()) - wm.setTime(DATETIME) - wm_file_path = prepareWeatherModel(wm, DATETIME, aoi.bounds()) + wm = Model() + wm.set_latlon_bounds(aoi.bounds()) + wm.setTime(DATETIME) + wm.set_wmLoc(str(tmp_path)) + wm_file_path = prepareWeatherModel(wm, DATETIME, aoi.bounds()) - with xr.open_dataset(wm_file_path) as ds: - wm_levels = ds['z'].values - wm_proj = CRS.from_wkt(ds['proj'].attrs['crs_wkt']) + with xr.open_dataset(wm_file_path) as ds: + wm_levels = ds['z'].values + wm_proj = CRS.from_wkt(ds['proj'].attrs['crs_wkt']) - assert not hasattr(aoi, 'xpts') + assert not hasattr(aoi, 'xpts') - ds = _get_delays_on_cube(DATETIME, wm_file_path, wm_proj, aoi, wm_levels, los, wm_proj, ZREF) + ds = _get_delays_on_cube(DATETIME, wm_file_path, wm_proj, aoi, wm_levels, los, wm_proj, ZREF) - assert len(ds['x']) > 0 - assert ds['hydro'].mean() > 0 + assert len(ds['x']) > 0 + assert ds['hydro'].mean() > 0 diff --git a/test/test_slant.py b/test/test_slant.py index e13f0b530..0f575a3cd 100644 --- a/test/test_slant.py +++ b/test/test_slant.py @@ -1,96 +1,89 @@ -from RAiDER.cli.raider import calcDelays -import pytest -import glob -import os -import subprocess -import shutil +from pathlib import Path import numpy as np +import pytest import xarray as xr -from test import ( - TEST_DIR, WM_DIR, ORB_DIR, make_delay_name -) +from RAiDER.cli.raider import calcDelays from RAiDER.utilFcns import write_yaml +from test import ORB_DIR, WM_DIR, make_delay_name + @pytest.mark.parametrize('weather_model_name', ['ERA5']) -def test_slant_proj(weather_model_name): - SCENARIO_DIR = os.path.join(TEST_DIR, "scenario_3") - os.makedirs(SCENARIO_DIR, exist_ok=True) +def test_slant_proj(tmp_path: Path, weather_model_name): + scenario_dir = tmp_path / 'scenario_3' + scenario_dir.mkdir(exist_ok=True) ## make the lat lon grid S, N, W, E = 33, 34, -118.25, -116.75 - date = 20200130 - time ='13:52:45' + date = 20200130 + time = '13:52:45' ## make the run config file grp = { - 'date_group': {'date_start': date}, - 'height_group': {'height_levels': [0, 100, 500, 1000]}, - 'time_group': {'time': time, 'interpolate_time': 'none'}, - 'weather_model': weather_model_name, - 'aoi_group': {'bounding_box': [S, N, W, E]}, - 'runtime_group': {'output_directory': SCENARIO_DIR, - 'weather_model_directory': WM_DIR, - }, - 'los_group' : {'ray_trace': False, - 'orbit_file': os.path.join(ORB_DIR, - 'S1B_OPER_AUX_POEORB_OPOD_20210317T025713_'\ - 'V20200129T225942_20200131T005942.EOF') - } - } - - ## generate the default run config file and overwrite it with new parms - cfg = write_yaml(grp, 'temp.yaml') + 'date_group': {'date_start': date}, + 'height_group': {'height_levels': [0, 100, 500, 1000]}, + 'time_group': {'time': time, 'interpolate_time': 'none'}, + 'weather_model': weather_model_name, + 'aoi_group': {'bounding_box': [S, N, W, E]}, + 'runtime_group': { + 'output_directory': scenario_dir, + 'weather_model_directory': WM_DIR, + }, + 'los_group': { + 'ray_trace': False, + 'orbit_file': ( + Path(ORB_DIR) / 'S1B_OPER_AUX_POEORB_OPOD_20210317T025713_V20200129T225942_20200131T005942.EOF' + ), + }, + } + + ## generate the default run config file and overwrite it with new params + cfg = write_yaml(grp, scenario_dir / 'temp.yaml') ## run raider and intersect calcDelays([str(cfg)]) gold = {'ERA5': [33.4, -117.8, 0, 2.333865144]} lat, lon, hgt, val = gold[weather_model_name] - path_delays = os.path.join(SCENARIO_DIR, - make_delay_name(weather_model_name, date, time, 'std')) + path_delays = scenario_dir / make_delay_name(weather_model_name, date, time, 'std') with xr.open_dataset(path_delays) as ds: - delay = (ds['hydro'] + ds['wet']).sel( - y=lat, x=lon, z=hgt, method='nearest').item() + delay = (ds['hydro'] + ds['wet']).sel(y=lat, x=lon, z=hgt, method='nearest').item() np.testing.assert_almost_equal(val, delay) - # Clean up files - shutil.rmtree(SCENARIO_DIR) - [os.remove(f) for f in glob.glob(f'{weather_model_name}*')] - os.remove('temp.yaml') - @pytest.mark.parametrize('weather_model_name', ['ERA5']) -def test_ray_tracing(weather_model_name): - SCENARIO_DIR = os.path.join(TEST_DIR, "scenario_3") - os.makedirs(SCENARIO_DIR, exist_ok=True) +def test_ray_tracing(tmp_path: Path, weather_model_name): + scenario_dir = tmp_path / 'scenario_3' + scenario_dir.mkdir(exist_ok=True) ## make the lat lon grid S, N, W, E = 33, 34, -118.25, -117.25 - date = 20200130 - time ='13:52:45' + date = 20200130 + time = '13:52:45' ## make the run config file grp = { - 'date_group': {'date_start': date}, - 'height_group': {'height_levels': [0, 100, 500, 1000]}, - 'time_group': {'time': time, 'interpolate_time': 'none'}, - 'weather_model': weather_model_name, - 'aoi_group': {'bounding_box': [S, N, W, E]}, - 'runtime_group': {'output_directory': SCENARIO_DIR, - 'weather_model_directory': WM_DIR, - }, - 'los_group' : {'ray_trace': True, - 'orbit_file': os.path.join(ORB_DIR, - 'S1B_OPER_AUX_POEORB_OPOD_20210317T025713_'\ - 'V20200129T225942_20200131T005942.EOF') - } - } - - ## generate the default run config file and overwrite it with new parms - cfg = write_yaml(grp, 'temp.yaml') + 'date_group': {'date_start': date}, + 'height_group': {'height_levels': [0, 100, 500, 1000]}, + 'time_group': {'time': time, 'interpolate_time': 'none'}, + 'weather_model': weather_model_name, + 'aoi_group': {'bounding_box': [S, N, W, E]}, + 'runtime_group': { + 'output_directory': scenario_dir, + 'weather_model_directory': WM_DIR, + }, + 'los_group': { + 'ray_trace': True, + 'orbit_file': ( + Path(ORB_DIR) / 'S1B_OPER_AUX_POEORB_OPOD_20210317T025713_V20200129T225942_20200131T005942.EOF' + ), + }, + } + + ## generate the default run config file and overwrite it with new params + cfg = write_yaml(grp, scenario_dir / 'temp.yaml') ## run raider and intersect calcDelays([str(cfg)]) @@ -99,15 +92,7 @@ def test_ray_tracing(weather_model_name): gold = {'ERA5': [33.4, -117.8, 0, 2.97711681]} lat, lon, hgt, val = gold[weather_model_name] - path_delays = os.path.join(SCENARIO_DIR, - make_delay_name(weather_model_name, date, time, 'ray')) + path_delays = scenario_dir / make_delay_name(weather_model_name, date, time, 'ray') with xr.open_dataset(path_delays) as ds: - delay = (ds['hydro'] + ds['wet']).sel( - y=lat, x=lon, z=hgt, method='nearest').item() + delay = (ds['hydro'] + ds['wet']).sel(y=lat, x=lon, z=hgt, method='nearest').item() np.testing.assert_almost_equal(val, delay) - - # Clean up files - shutil.rmtree(SCENARIO_DIR) - [os.remove(f) for f in glob.glob(f'{weather_model_name}*')] - os.remove('temp.yaml') - diff --git a/test/test_synthetic.py b/test/test_synthetic.py index 1bd4b3059..ef419fcd1 100644 --- a/test/test_synthetic.py +++ b/test/test_synthetic.py @@ -1,10 +1,10 @@ import os import os.path as op -import shutil import subprocess from dataclasses import dataclass from datetime import datetime from pathlib import Path +from typing import Union import numpy as np import pytest @@ -16,7 +16,7 @@ from RAiDER.losreader import Raytracing, build_ray from RAiDER.models.weatherModel import make_weather_model_filename from RAiDER.utilFcns import lla2ecef, write_yaml -from test import ORB_DIR, TEST_DIR, WM_DIR, pushd +from test import ORB_DIR, WM_DIR def update_model(wm_file: str, wm_eq_type: str, wm_dir: str = "weather_files_synth"): @@ -107,10 +107,10 @@ class StudyArea(object): Fort (Fortaleza, Brazil; equator) """ - def __init__(self, region: str, wmName: str, path: str): + def __init__(self, region: str, wmName: str, path: Union[Path, str]): self.region = region self.wmName = wmName - self.wd = op.join(path, "synthetic_test") + self.wd = str(path) self.orb_dir = ORB_DIR self.setup_region() @@ -197,25 +197,24 @@ def test_dl_real(tmp_path, region, mod="ERA5"): This 'golden dataset' shouldnt be changed """ - with pushd(tmp_path): - SAobj = StudyArea(region, mod, tmp_path) - dct_cfg = SAobj.make_config_dict() - # set the real weather model path and download only - dct_cfg["runtime_group"]["weather_model_directory"] = op.dirname( - SAobj.path_wm_real - ) - dct_cfg["download_only"] = True + SAobj = StudyArea(region, mod, tmp_path) + dct_cfg = SAobj.make_config_dict() + # set the real weather model path and download only + dct_cfg["runtime_group"]["weather_model_directory"] = op.dirname( + SAobj.path_wm_real + ) + dct_cfg["download_only"] = True - cfg = write_yaml(dct_cfg, 'temp.yaml') + cfg = write_yaml(dct_cfg, tmp_path / 'temp.yaml') - ## run raider to download the real weather model - cmd = f'raider.py {cfg}' - proc = subprocess.run(cmd.split(), stdout=subprocess.PIPE, universal_newlines=True) - assert proc.returncode == 0, 'RAiDER did not complete successfully' + ## run raider to download the real weather model + cmd = f'raider.py {cfg}' + proc = subprocess.run(cmd.split(), stdout=subprocess.PIPE, universal_newlines=True) + assert proc.returncode == 0, 'RAiDER did not complete successfully' @pytest.mark.parametrize("region", "AK LA Fort".split()) -def test_hydrostatic_eq(tmp_path, region, mod="ERA-5"): +def test_hydrostatic_eq(tmp_path: Path, region, mod="ERA-5"): """Test hydrostatic equation: Hydro Refractivity = k1 * (Pressure/Temp). The hydrostatic delay reduces to an integral along the ray path when P=T. @@ -232,7 +231,7 @@ def test_hydrostatic_eq(tmp_path, region, mod="ERA-5"): significantly different = 6 decimal places """ ## setup the config files - SAobj = StudyArea(region, mod, TEST_DIR) + SAobj = StudyArea(region, mod, tmp_path) dct_cfg = SAobj.make_config_dict() dct_cfg["runtime_group"]["weather_model_directory"] = SAobj.wm_dir_synth dct_cfg["download_only"] = False @@ -241,7 +240,7 @@ def test_hydrostatic_eq(tmp_path, region, mod="ERA-5"): update_model(SAobj.path_wm_real, "hydro", SAobj.wm_dir_synth) ## run raider with the synthetic model - cfg = write_yaml(dct_cfg, 'temp.yaml') + cfg = write_yaml(dct_cfg, tmp_path / 'temp.yaml') calcDelays([str(cfg)]) # get the just created synthetic delays @@ -278,8 +277,9 @@ def test_hydrostatic_eq(tmp_path, region, mod="ERA-5"): @pytest.mark.parametrize("region", "AK LA Fort".split()) -def test_wet_eq_linear(tmp_path, region, mod="ERA-5"): +def test_wet_eq_linear(tmp_path: Path, region, mod="ERA-5"): """Test linear part of wet equation. + Wet Refractivity = k2 * (E/T) + k3 * (E/T^2) E = relative humidty; T = temperature. @@ -296,67 +296,54 @@ def test_wet_eq_linear(tmp_path, region, mod="ERA-5"): Ensure that normalized residual is not significantly different from 0 significantly different = 7 decimal places """ - with pushd(tmp_path): - # create temp directory for file that is created - dir_to_del = "tmp_dir" - if not os.path.exists(dir_to_del): - os.mkdir(dir_to_del) - - ## setup the config files - SAobj = StudyArea(region, mod, dir_to_del) - dct_cfg = SAobj.make_config_dict() - dct_cfg["runtime_group"]["weather_model_directory"] = SAobj.wm_dir_synth - dct_cfg["download_only"] = False - - ## update the weather model; t = e for wet1 - update_model(SAobj.path_wm_real, "wet_linear", SAobj.wm_dir_synth) - - ## run raider with the synthetic model - cfg = write_yaml(dct_cfg, 'temp.yaml') - calcDelays([str(cfg)]) - - # get the just created synthetic delays - wm_name = SAobj.wmName.replace("-", "") # incase of ERA-5 - ds = xr.open_dataset( - f'{SAobj.wd}/{wm_name}_tropo_{SAobj.dts.replace("_", "")}_ray.nc' - ) - da = ds["wet"] - ds.close() - del ds - - # now build the rays at the unbuffered wm nodes - max_tropo_height = SAobj.wmObj._zlevels[-1] - 1 - targ_xyz = [da.x.data, da.y.data, da.z.data] - ray_length = length_of_ray( - targ_xyz, SAobj.wmObj._zlevels, SAobj.los, max_tropo_height - ) + ## setup the config files + SAobj = StudyArea(region, mod, tmp_path) + dct_cfg = SAobj.make_config_dict() + dct_cfg["runtime_group"]["weather_model_directory"] = SAobj.wm_dir_synth + dct_cfg["download_only"] = False - # scale by constant (units K/Pa) to match raider (m K / Pa) - ray_data = ray_length * SAobj.wmObj._k2 + ## update the weather model; t = e for wet1 + update_model(SAobj.path_wm_real, "wet_linear", SAobj.wm_dir_synth) - # actual raider data - # undo scaling of ppm; units are meters * K/Pa - raid_data = da.data * 1e6 + ## run raider with the synthetic model + cfg = write_yaml(dct_cfg, tmp_path / 'temp.yaml') + calcDelays([str(cfg)]) - assert np.all(np.abs(ray_data) > 1) - assert np.all(np.abs(raid_data) > 1) + # get the just created synthetic delays + wm_name = SAobj.wmName.replace("-", "") # incase of ERA-5 + ds = xr.open_dataset( + f'{SAobj.wd}/{wm_name}_tropo_{SAobj.dts.replace("_", "")}_ray.nc' + ) + da = ds["wet"] + ds.close() + del ds - # normalize with the theoretical data and compare difference with 0 - resid = (ray_data - raid_data) / ray_data - np.testing.assert_almost_equal(0, resid, decimal=6) + # now build the rays at the unbuffered wm nodes + max_tropo_height = SAobj.wmObj._zlevels[-1] - 1 + targ_xyz = [da.x.data, da.y.data, da.z.data] + ray_length = length_of_ray( + targ_xyz, SAobj.wmObj._zlevels, SAobj.los, max_tropo_height + ) - da.close() - del da + # scale by constant (units K/Pa) to match raider (m K / Pa) + ray_data = ray_length * SAobj.wmObj._k2 - # delete temp directory - if os.path.exists(dir_to_del): - shutil.rmtree(dir_to_del) + # actual raider data + # undo scaling of ppm; units are meters * K/Pa + raid_data = da.data * 1e6 + + assert np.all(np.abs(ray_data) > 1) + assert np.all(np.abs(raid_data) > 1) + + # normalize with the theoretical data and compare difference with 0 + resid = (ray_data - raid_data) / ray_data + np.testing.assert_almost_equal(0, resid, decimal=6) @pytest.mark.parametrize("region", "AK LA Fort".split()) -def test_wet_eq_nonlinear(tmp_path, region, mod="ERA-5"): - """Test the nonlinear part of the wet equation.""" - """ +def test_wet_eq_nonlinear(tmp_path: Path, region, mod="ERA-5"): + """Test the nonlinear part of the wet equation. + Wet Refractivity = k2 * (E/T) + k3 * (E/T^2) E = relative humidty; T = temperature @@ -372,61 +359,44 @@ def test_wet_eq_nonlinear(tmp_path, region, mod="ERA-5"): We ensure that normalized residual is not significantly different from 0 significantly different = 6 decimal places """ + ## setup the config files + SAobj = StudyArea(region, mod, tmp_path) + dct_cfg = SAobj.make_config_dict() + dct_cfg["runtime_group"]["weather_model_directory"] = SAobj.wm_dir_synth + dct_cfg["download_only"] = False - with pushd(tmp_path): - # create temporary directory for files created in function - dir_to_del = "tmp_dir" - if not os.path.exists(dir_to_del): - os.mkdir(dir_to_del) - - ## setup the config files - SAobj = StudyArea(region, mod, dir_to_del) - dct_cfg = SAobj.make_config_dict() - dct_cfg["runtime_group"]["weather_model_directory"] = SAobj.wm_dir_synth - dct_cfg["download_only"] = False - - ## update the weather model; t = e for wet1 - update_model(SAobj.path_wm_real, "wet_nonlinear", SAobj.wm_dir_synth) - - ## run raider with the synthetic model - cfg = write_yaml(dct_cfg, 'temp.yaml') - calcDelays([str(cfg)]) - - # get the just created synthetic delays - wm_name = SAobj.wmName.replace("-", "") # incase of ERA-5 - ds = xr.open_dataset( - f'{SAobj.wd}/{wm_name}_tropo_{SAobj.dts.replace("_", "")}_ray.nc' - ) - da = ds["wet"] - ds.close() - del ds - - # now build the rays at the unbuffered wm nodes - max_tropo_height = SAobj.wmObj._zlevels[-1] - 1 - targ_xyz = [da.x.data, da.y.data, da.z.data] - ray_length = length_of_ray( - targ_xyz, SAobj.wmObj._zlevels, SAobj.los, max_tropo_height - ) - # scale by constant (units K/Pa) to match raider (m K^2 / Pa) - ray_data = ray_length * SAobj.wmObj._k3 + ## update the weather model; t = e for wet1 + update_model(SAobj.path_wm_real, "wet_nonlinear", SAobj.wm_dir_synth) - # actual raider data - # undo scaling of ppm; units are meters * K^2 /Pa - raid_data = da.data * 1e6 + ## run raider with the synthetic model + cfg = write_yaml(dct_cfg, tmp_path / 'temp.yaml') + calcDelays([str(cfg)]) + + # get the just created synthetic delays + wm_name = SAobj.wmName.replace("-", "") # incase of ERA-5 + ds = xr.open_dataset( + f'{SAobj.wd}/{wm_name}_tropo_{SAobj.dts.replace("_", "")}_ray.nc' + ) + da = ds["wet"] + # ds.close() + # del ds - assert np.all(np.abs(ray_data) > 1) - assert np.all(np.abs(raid_data) > 1) + # now build the rays at the unbuffered wm nodes + max_tropo_height = SAobj.wmObj._zlevels[-1] - 1 + targ_xyz = [da.x.data, da.y.data, da.z.data] + ray_length = length_of_ray( + targ_xyz, SAobj.wmObj._zlevels, SAobj.los, max_tropo_height + ) + # scale by constant (units K/Pa) to match raider (m K^2 / Pa) + ray_data = ray_length * SAobj.wmObj._k3 - # normalize with the theoretical data and compare difference with 0 - resid = (ray_data - raid_data) / ray_data - np.testing.assert_almost_equal(0, resid, decimal=6) + # actual raider data + # undo scaling of ppm; units are meters * K^2 /Pa + raid_data = da.data * 1e6 - da.close() - Path('./temp.yaml').unlink(missing_ok=True) - Path('./error.log').unlink(missing_ok=True) - Path('./debug.log').unlink(missing_ok=True) - del da + assert np.all(np.abs(ray_data) > 1) + assert np.all(np.abs(raid_data) > 1) - # delete temp directory - if os.path.exists(dir_to_del): - shutil.rmtree(dir_to_del) + # normalize with the theoretical data and compare difference with 0 + resid = (ray_data - raid_data) / ray_data + np.testing.assert_almost_equal(0, resid, decimal=6) diff --git a/test/test_temporal_interpolate.py b/test/test_temporal_interpolate.py index 76e2cd129..e0d5a503c 100644 --- a/test/test_temporal_interpolate.py +++ b/test/test_temporal_interpolate.py @@ -1,27 +1,21 @@ -import pytest -import glob -import shutil -import os +from pathlib import Path + import numpy as np +import pytest import xarray as xr - -from test import ( - WM, TEST_DIR -) - +from RAiDER.cli.raider import calcDelays from RAiDER.logger import logger from RAiDER.utilFcns import write_yaml -from RAiDER.cli.raider import calcDelays +from test import WM + wm = 'ERA5' if WM == 'ERA-5' else WM @pytest.mark.long -def test_cube_timemean(): - """ Test the mean interpolation by computing cube delays at 1:30PM vs mean of 12 PM / 3PM for GMAO """ - SCENARIO_DIR = os.path.join(TEST_DIR, "INTERP_TIME") - os.makedirs(SCENARIO_DIR, exist_ok=True) +def test_cube_timemean(tmp_path: Path): + """Test the mean interpolation by computing cube delays at 1:30PM vs mean of 12 PM / 3PM for GMAO.""" ## make the lat lon grid S, N, W, E = 34, 35, -117, -116 date = 20200130 @@ -33,50 +27,40 @@ def test_cube_timemean(): 'weather_model': WM, 'aoi_group': {'bounding_box': [S, N, W, E]}, 'time_group': {'interpolate_time': 'none'}, - 'runtime_group': {'output_directory': SCENARIO_DIR}, + 'runtime_group': {'output_directory': tmp_path}, } - ## run raider without interpolation for two exact weather model times for hr in [hr1, hr2]: grp['time_group'].update({'time': f'{hr}:00:00'}) ## generate the default run config file and overwrite it with new parms - cfg = write_yaml(grp, 'temp.yaml') + cfg = write_yaml(grp, tmp_path / 'temp1.yaml') + ## run raider for the default date calcDelays([str(cfg)]) ## run interpolation in the middle of the two grp['time_group'] = {'time': ti, 'interpolate_time': 'center_time'} - cfg = write_yaml(grp, 'temp.yaml') + cfg = write_yaml(grp, tmp_path / 'temp.yaml') calcDelays([str(cfg)]) - with xr.open_dataset(os.path.join(SCENARIO_DIR, f'{WM}_tropo_{date}T{hr1}0000_ztd.nc')) as ds: + with xr.open_dataset(tmp_path / f'{WM}_tropo_{date}T{hr1}0000_ztd.nc') as ds: da1_tot = ds['wet'] + ds['hydro'] - with xr.open_dataset(os.path.join(SCENARIO_DIR, f'{WM}_tropo_{date}T{hr2}0000_ztd.nc')) as ds: + with xr.open_dataset(tmp_path/ f'{WM}_tropo_{date}T{hr2}0000_ztd.nc') as ds: da2_tot = ds['wet'] + ds['hydro'] - with xr.open_dataset(os.path.join(SCENARIO_DIR, f'{WM}_tropo_{date}T{ti.replace(":", "")}_ztd.nc')) as ds: + with xr.open_dataset(tmp_path/ f'{WM}_tropo_{date}T{ti.replace(":", "")}_ztd.nc') as ds: da_interp_tot = ds['wet'] + ds['hydro'] da_mu = (da1_tot + da2_tot) / 2 assert np.allclose(da_mu, da_interp_tot) - # Clean up files - shutil.rmtree(SCENARIO_DIR) - [os.remove(f) for f in glob.glob(f'{WM}*')] - os.remove('temp.yaml') - - return - - @pytest.mark.long -def test_cube_weighting(): - """ Test the weighting by comparing a small crop with numpy directly """ - from datetime import datetime - SCENARIO_DIR = os.path.join(TEST_DIR, "INTERP_TIME") - os.makedirs(SCENARIO_DIR, exist_ok=True) +def test_cube_weighting(tmp_path: Path): + """Test the weighting by comparing a small crop with numpy directly.""" + import datetime as dt ## make the lat lon grid S, N, W, E = 34, 35, -117, -116 date = 20200130 @@ -88,7 +72,7 @@ def test_cube_weighting(): 'weather_model': WM, 'aoi_group': {'bounding_box': [S, N, W, E]}, 'time_group': {'interpolate_time': 'none'}, - 'runtime_group': {'output_directory': SCENARIO_DIR}, + 'runtime_group': {'output_directory': tmp_path}, } @@ -96,30 +80,30 @@ def test_cube_weighting(): for hr in [hr1, hr2]: grp['time_group'].update({'time': f'{hr}:00:00'}) ## generate the default run config file and overwrite it with new parms - cfg = write_yaml(grp, 'temp.yaml') + cfg = write_yaml(grp, tmp_path / 'temp1.yaml') ## run raider for the default date calcDelays([str(cfg)]) ## run interpolation very near the first grp['time_group'] = {'time': ti, 'interpolate_time': 'center_time'} - cfg = write_yaml(grp, 'temp.yaml') + cfg = write_yaml(grp, tmp_path / 'temp2.yaml') calcDelays([str(cfg)]) ## double check on weighting - with xr.open_dataset(os.path.join(SCENARIO_DIR, f'{WM}_tropo_{date}T{hr1}0000_ztd.nc')) as ds: + with xr.open_dataset(tmp_path / f'{WM}_tropo_{date}T{hr1}0000_ztd.nc') as ds: da1_tot = ds['wet'] + ds['hydro'] - with xr.open_dataset(os.path.join(SCENARIO_DIR, f'{WM}_tropo_{date}T{hr2}0000_ztd.nc')) as ds: + with xr.open_dataset(tmp_path / f'{WM}_tropo_{date}T{hr2}0000_ztd.nc') as ds: da2_tot = ds['wet'] + ds['hydro'] - with xr.open_dataset(os.path.join(SCENARIO_DIR, f'{WM}_tropo_{date}T{ti.replace(":", "")}_ztd.nc')) as ds: + with xr.open_dataset(tmp_path / f'{WM}_tropo_{date}T{ti.replace(":", "")}_ztd.nc') as ds: da_interp_tot = ds['wet'] + ds['hydro'] - dt1 = datetime.strptime(f'{date}{hr1}', '%Y%m%d%H') - dt2 = datetime.strptime(f'{date}{hr2}', '%Y%m%d%H') - dt_ref = datetime.strptime(f'{date}{ti}', '%Y%m%d%H:%M:%S') + dt1 = dt.datetime.strptime(f'{date}{hr1}', '%Y%m%d%H') + dt2 = dt.datetime.strptime(f'{date}{hr2}', '%Y%m%d%H') + dt_ref = dt.datetime.strptime(f'{date}{ti}', '%Y%m%d%H:%M:%S') wgts = np.array([(dt_ref-dt1).seconds, (dt2-dt_ref).seconds]) da1_crop = da1_tot.isel(z=0, y=slice(0,1), x=slice(0, 2)) @@ -133,10 +117,3 @@ def test_cube_weighting(): logger.info ('Data from two dates: %s', dat) logger.info ('Weighted mean: %s', da_out_crop.data) assert np.allclose(da_out_crop, np.average(dat, weights=1/wgts, axis=0)) - - # Clean up files - shutil.rmtree(SCENARIO_DIR) - [os.remove(f) for f in glob.glob(f'{WM}*')] - os.remove('temp.yaml') - - return diff --git a/test/test_util.py b/test/test_util.py index a0a0e5084..6d080aee5 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -35,7 +35,7 @@ writeArrayToRaster, writeWeatherVarsXarray, ) -from test import TEST_DIR, pushd +from test import TEST_DIR _R_EARTH = 6378138 @@ -170,11 +170,10 @@ def test_writeArrayToRaster_2(): def test_writeArrayToRaster_3(tmp_path): test = np.random.randn(10,10) test = test + test * 1j - with pushd(tmp_path): - path = tmp_path / 'tmp_file.tif' - writeArrayToRaster(test, path) - tmp = rio_profile(path) - assert tmp['dtype'] == 'complex64' + path = tmp_path / 'tmp_file.tif' + writeArrayToRaster(test, path) + tmp = rio_profile(path) + assert tmp['dtype'] == 'complex64' def test_writeArrayToRaster_4(tmp_path): @@ -182,18 +181,17 @@ def test_writeArrayToRaster_4(tmp_path): geotif = SCENARIO0_DIR / 'small_dem.tif' profile = rio_profile(geotif) data, _ = rio_open(geotif) - with pushd(tmp_path): - path = tmp_path / 'tmp_file.nc' - writeArrayToRaster( - data, - path, - proj=profile['crs'], - gt=profile['transform'], - fmt='nc', - ) - new_path = tmp_path / 'tmp_file.tif' - prof = rio_profile(new_path) - assert prof['driver'] == 'GTiff' + path = tmp_path / 'tmp_file.nc' + writeArrayToRaster( + data, + path, + proj=profile['crs'], + gt=profile['transform'], + fmt='nc', + ) + new_path = tmp_path / 'tmp_file.tif' + prof = rio_profile(new_path) + assert prof['driver'] == 'GTiff' def test_makePoints0D_cython(make_points_0d_data): @@ -271,9 +269,9 @@ def test_least_nonzero_2(): ) -def test_rio_extent(): +def test_rio_extent(tmp_path: Path): # Create a simple georeferenced test file - test_file = Path("test.tif") + test_file = tmp_path / 'test.tif' with rasterio.open(test_file, mode="w", width=11, height=11, count=1, dtype=np.float64, crs=pyproj.CRS.from_epsg(4326), @@ -283,7 +281,6 @@ def test_rio_extent(): dst.write(np.random.randn(11, 11), 1) profile = rio_profile(test_file) assert rio_extents(profile) == (17.0, 18.0, 17.0, 18.0) - test_file.unlink() def test_getTimeFromFile(): diff --git a/test/test_weather_model.py b/test/test_weather_model.py index 6b76bcfa5..dc8842108 100644 --- a/test/test_weather_model.py +++ b/test/test_weather_model.py @@ -1,31 +1,31 @@ -from collections.abc import Iterable -import datetime +import datetime as dt import operator -import pytest +from collections.abc import Iterable +from functools import reduce +from pathlib import Path from typing import TypeVar import numpy as np -from functools import reduce -from numpy import nan +import pytest from scipy.interpolate import RegularGridInterpolator as rgi -from pathlib import Path from RAiDER.constants import _ZMIN, _ZREF -from RAiDER.models.weatherModel import ( - WeatherModel, - find_svp, - make_raw_weather_data_filename, - make_weather_model_filename, +from RAiDER.models.customExceptions import ( + DatetimeOutsideRange, + NoWeatherModelData, ) from RAiDER.models.era5 import ERA5 from RAiDER.models.era5t import ERA5T +from RAiDER.models.gmao import GMAO from RAiDER.models.hres import HRES from RAiDER.models.hrrr import HRRR, HRRRAK, get_bounds_indices -from RAiDER.models.gmao import GMAO from RAiDER.models.merra2 import MERRA2 from RAiDER.models.ncmr import NCMR -from RAiDER.models.customExceptions import ( - DatetimeOutsideRange, NoWeatherModelData, +from RAiDER.models.weatherModel import ( + WeatherModel, + find_svp, + make_raw_weather_data_filename, + make_weather_model_filename, ) @@ -33,54 +33,6 @@ _LAT0 = 0 -@pytest.fixture -def era5(): - wm = ERA5() - return wm - - -@pytest.fixture -def era5t(): - wm = ERA5T() - return wm - - -@pytest.fixture -def hres(): - wm = HRES() - return wm - - -@pytest.fixture -def gmao(): - wm = GMAO() - return wm - - -@pytest.fixture -def merra2(): - wm = MERRA2() - return wm - - -@pytest.fixture -def hrrr(): - wm = HRRR() - return wm - - -@pytest.fixture -def hrrrak(): - wm = HRRRAK() - return wm - - -@pytest.fixture -def ncmr(): - wm = NCMR() - return wm - - T = TypeVar('T') @@ -98,40 +50,43 @@ def __init__(self) -> None: # noqa: D107 self._k2 = 1 self._k3 = 1 - self._Name = "MOCK" - self._valid_range = (datetime.datetime(1970, 1, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())), - datetime.datetime.now(datetime.timezone.utc)) - self._lag_time = datetime.timedelta(days=15) + self._Name = 'MOCK' + self._valid_range = ( + dt.datetime(1970, 1, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta())), + dt.datetime.now(dt.timezone.utc), + ) + self._lag_time = dt.timedelta(days=15) def _fetch(self, ll_bounds, time, out): # noqa: ANN202 pass def load_weather(self, *args, **kwargs) -> None: # noqa: D102 _N_Z = 32 - self._ys = np.arange(-2,3) + _LAT0 - self._xs = np.arange(-3,4) + _LON0 + self._ys = np.arange(-2, 3) + _LAT0 + self._xs = np.arange(-3, 4) + _LON0 self._zs = np.linspace(0, 1e5, _N_Z) self._t = np.ones((len(self._ys), len(self._xs), _N_Z)) self._e = self._t.copy() - self._e[:,3:,:] = 2 + self._e[:, 3:, :] = 2 _p = np.arange(31, -1, -1) self._p = np.broadcast_to(_p, self._t.shape) self._true_hydro_refr = np.broadcast_to(_p, (self._t.shape)) self._true_wet_ztd = 1e-6 * 2 * np.broadcast_to(np.flip(self._zs), (self._t.shape)) - self._true_wet_ztd[:,3:] = 2 * self._true_wet_ztd[:,3:] + self._true_wet_ztd[:, 3:] = 2 * self._true_wet_ztd[:, 3:] self._true_hydro_ztd = np.zeros(self._t.shape) for layer in range(len(self._zs)): - self._true_hydro_ztd[:,:,layer] = 1e-6 * 0.5 * (self._zs[-1] - self._zs[layer]) * _p[layer] + self._true_hydro_ztd[:, :, layer] = 1e-6 * 0.5 * (self._zs[-1] - self._zs[layer]) * _p[layer] self._true_wet_refr = 2 * np.ones(self._t.shape) - self._true_wet_refr[:,3:] = 4 + self._true_wet_refr[:, 3:] = 4 def interpWet(self): # noqa: ANN201, D102 _ifWet = rgi((self._ys, self._xs, self._zs), self._true_wet_refr) return _ifWet + def interpHydro(self): # noqa: ANN201, D102 _ifHydro = rgi((self._ys, self._xs, self._zs), self._true_hydro_refr) return _ifHydro @@ -141,6 +96,7 @@ def interpHydro(self): # noqa: ANN201, D102 def model(): # noqa: ANN201, D103 return MockWeatherModel() + def test_weatherModel_basic1(model: MockWeatherModel) -> None: """Test uniform_in_z.""" wm = model @@ -151,34 +107,36 @@ def test_weatherModel_basic1(model: MockWeatherModel) -> None: # check some defaults assert wm._humidityType == 'q' - wm.setTime(datetime.datetime(2020, 1, 1, 6, 0, 0)) - assert wm._time == datetime.datetime(2020, 1, 1, 6, 0, 0).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + wm.setTime(dt.datetime(2020, 1, 1, 6, 0, 0)) + assert wm._time == dt.datetime(2020, 1, 1, 6, 0, 0).replace(tzinfo=dt.timezone(offset=dt.timedelta())) wm.setTime('2020-01-01T00:00:00') - assert wm._time == datetime.datetime(2020, 1, 1, 0, 0, 0).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._time == dt.datetime(2020, 1, 1, 0, 0, 0).replace(tzinfo=dt.timezone(offset=dt.timedelta())) wm.setTime('19720229', fmt='%Y%m%d') # test a leap year - assert wm._time == datetime.datetime(1972, 2, 29, 0, 0, 0).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._time == dt.datetime(1972, 2, 29, 0, 0, 0).replace(tzinfo=dt.timezone(offset=dt.timedelta())) with pytest.raises(DatetimeOutsideRange): - wm.checkTime(datetime.datetime(1950, 1, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) + wm.checkTime(dt.datetime(1950, 1, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta()))) - wm.checkTime(datetime.datetime(2000, 1, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) + wm.checkTime(dt.datetime(2000, 1, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta()))) with pytest.raises(DatetimeOutsideRange): - wm.checkTime(datetime.datetime.now().replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) + wm.checkTime(dt.datetime.now().replace(tzinfo=dt.timezone(offset=dt.timedelta()))) def test_uniform_in_z_small(model: MockWeatherModel) -> None: """Test uniform_in_z.""" # Uneven z spacing, but averages to [1, 2] + # fmt: off model._zs = np.array([ - [[1., 2.], + [[1.0, 2.0], [0.9, 1.1]], - [[1., 2.6], + [[1.0, 2.6], [1.1, 2.3]] ]) + # fmt: on model._xs = np.array([1, 2, 2]) model._ys = np.array([2, 3, 2]) model._p = np.arange(8).reshape(2, 2, 2) @@ -191,13 +149,15 @@ def test_uniform_in_z_small(model: MockWeatherModel) -> None: # Note that when the lower bound is exactly equal we get a value, but # when the upper bound is exactly equal we get the fill + # fmt: off interpolated = np.array([ - [[0, nan], - [2.5, nan]], + [[0.0, np.nan], + [2.5, np.nan]], - [[4., 4.625], - [nan, 6.75]] + [[4.0, 4.625], + [np.nan, 6.75 ]] ]) + # fmt: on assert np.allclose(model._p, interpolated, equal_nan=True, rtol=0) assert np.allclose(model._t, interpolated * 2, equal_nan=True, rtol=0) @@ -230,12 +190,9 @@ def test_uniform_in_z_large(model: MockWeatherModel) -> None: interpolated = np.tile(np.arange(y), (x, 1)) - assert np.allclose(np.nanmean(model._p, axis=-1), - interpolated, equal_nan=True, rtol=0) - assert np.allclose(np.nanmean(model._t, axis=-1), - interpolated * 2, equal_nan=True, rtol=0) - assert np.allclose(np.nanmean(model._e, axis=-1), - interpolated * 3, equal_nan=True, rtol=0) + assert np.allclose(np.nanmean(model._p, axis=-1), interpolated, equal_nan=True, rtol=0) + assert np.allclose(np.nanmean(model._t, axis=-1), interpolated * 2, equal_nan=True, rtol=0) + assert np.allclose(np.nanmean(model._e, axis=-1), interpolated * 3, equal_nan=True, rtol=0) assert np.allclose(model._zs, zlevels, atol=0.05, rtol=0) @@ -243,45 +200,43 @@ def test_uniform_in_z_large(model: MockWeatherModel) -> None: def test_mwmf() -> None: """Test making a raw weather model filename.""" name = 'ERA-5' - time = datetime.datetime(2020, 1, 1) + time = dt.datetime(2020, 1, 1) ll_bounds = (-90, 90, -180, 180) - assert make_weather_model_filename(name, time, ll_bounds) == \ - 'ERA-5_2020_01_01_T00_00_00_90S_90N_180W_180E.nc' + assert make_weather_model_filename(name, time, ll_bounds) == 'ERA-5_2020_01_01_T00_00_00_90S_90N_180W_180E.nc' def test_mrwmf() -> None: """Test making the raw weather model file using ERA-5.""" outLoc = './' name = 'ERA-5' - time = datetime.datetime(2020, 1, 1) - assert make_raw_weather_data_filename(outLoc, name, time) == \ - './ERA-5_2020_01_01_T00_00_00.nc' + time = dt.datetime(2020, 1, 1) + assert make_raw_weather_data_filename(outLoc, name, time) == './ERA-5_2020_01_01_T00_00_00.nc' -def test_era5(era5: ERA5) -> None: +def test_era5() -> None: """Test ERA-5.""" - wm = era5 + wm = ERA5() assert wm._humidityType == 'q' assert wm._Name == 'ERA-5' - assert wm._valid_range[0] == datetime.datetime(1950, 1, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(1950, 1, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert wm._proj.to_epsg() == 4326 -def test_era5t(era5t: ERA5T) -> None: +def test_era5t() -> None: """Test ERA-5.""" - wm = era5t + wm = ERA5T() assert wm._humidityType == 'q' assert wm._Name == 'ERA-5T' - assert wm._valid_range[0] == datetime.datetime(1950, 1, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(1950, 1, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert wm._proj.to_epsg() == 4326 -def test_hres(hres: HRES) -> None: +def test_hres() -> None: """Test HRES.""" - wm = hres + wm = HRES() assert wm._humidityType == 'q' assert wm._Name == 'HRES' - assert wm._valid_range[0] == datetime.datetime(1983, 4, 20).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(1983, 4, 20).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert wm._proj.to_epsg() == 4326 assert wm._levels == 137 @@ -289,34 +244,34 @@ def test_hres(hres: HRES) -> None: assert wm._levels == 91 -def test_gmao(gmao: GMAO) -> None: +def test_gmao() -> None: """Test GMAO.""" - wm = gmao + wm = GMAO() assert wm._humidityType == 'q' assert wm._Name == 'GMAO' - assert wm._valid_range[0] == datetime.datetime(2014, 2, 20).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(2014, 2, 20).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert wm._proj.to_epsg() == 4326 -def test_merra2(merra2: MERRA2) -> None: +def test_merra2() -> None: """Test MERRA-2.""" - wm = merra2 + wm = MERRA2() assert wm._humidityType == 'q' assert wm._Name == 'MERRA2' - assert wm._valid_range[0] == datetime.datetime(1980, 1, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(1980, 1, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert wm._proj.to_epsg() == 4326 -def test_hrrr(hrrr: HRRR) -> None: +def test_hrrr() -> None: """Test HRRR.""" - wm = hrrr + wm = HRRR() assert wm._humidityType == 'q' assert wm._Name == 'HRRR' - assert wm._valid_range[0] == datetime.datetime(2016, 7, 15).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(2016, 7, 15).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert wm._proj.to_epsg() is None with pytest.raises(DatetimeOutsideRange): - wm.checkTime(datetime.datetime(2010, 7, 15).replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) - wm.checkTime(datetime.datetime(2018, 7, 12).replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) + wm.checkTime(dt.datetime(2010, 7, 15).replace(tzinfo=dt.timezone(offset=dt.timedelta()))) + wm.checkTime(dt.datetime(2018, 7, 12).replace(tzinfo=dt.timezone(offset=dt.timedelta()))) assert isinstance(wm, HRRR) wm.checkValidBounds(np.array([35, 40, -95, -90])) @@ -325,11 +280,11 @@ def test_hrrr(hrrr: HRRR) -> None: wm.checkValidBounds(np.array([45, 47, 300, 310])) -def test_hrrrak(hrrrak: HRRRAK) -> None: +def test_hrrrak() -> None: """Test HRRR-AK.""" - wm = hrrrak + wm = HRRRAK() assert wm._Name == 'HRRR-AK' - assert wm._valid_range[0] == datetime.datetime(2018, 7, 13).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(2018, 7, 13).replace(tzinfo=dt.timezone(offset=dt.timedelta())) assert isinstance(wm, HRRRAK) wm.checkValidBounds(np.array([45, 47, 200, 210])) @@ -338,28 +293,30 @@ def test_hrrrak(hrrrak: HRRRAK) -> None: wm.checkValidBounds(np.array([15, 20, 265, 270])) with pytest.raises(DatetimeOutsideRange): - wm.checkTime(datetime.datetime(2018, 7, 12).replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) + wm.checkTime(dt.datetime(2018, 7, 12).replace(tzinfo=dt.timezone(offset=dt.timedelta()))) - wm.checkTime(datetime.datetime(2018, 7, 15).replace(tzinfo=datetime.timezone(offset=datetime.timedelta()))) + wm.checkTime(dt.datetime(2018, 7, 15).replace(tzinfo=dt.timezone(offset=dt.timedelta()))) -def test_ncmr(ncmr: NCMR) -> None: - """Test NCMR""" - wm = ncmr +def test_ncmr() -> None: + """Test NCMR.""" + wm = NCMR() assert wm._humidityType == 'q' assert wm._Name == 'NCMR' - assert wm._valid_range[0] == datetime.datetime(2015, 12, 1).replace(tzinfo=datetime.timezone(offset=datetime.timedelta())) + assert wm._valid_range[0] == dt.datetime(2015, 12, 1).replace(tzinfo=dt.timezone(offset=dt.timedelta())) def test_find_svp() -> None: """Test the svp function.""" t = np.arange(0, 100, 10) + 273.15 svp_test = find_svp(t) + # fmt: off svp_true = np.array([ - 611.21, 1227.5981, 2337.2825, 4243.5093, - 7384.1753, 12369.2295, 20021.443, 31419.297, - 47940.574, 71305.16 + 611.21, 1227.5981, 2337.2825, 4243.5093, + 7384.1753, 12369.2295, 20021.443, 31419.297, + 47940.574, 71305.16 ]) + # fmt: on assert np.allclose(svp_test, svp_true) @@ -401,7 +358,7 @@ def test_get_bounds_indices_2() -> None: """Test bounds indices.""" snwe = [-10, 10, 170, -170] l = np.arange(-20, 20) - l2 = (((np.arange(160, 200) + 180) % 360) - 180) + l2 = ((np.arange(160, 200) + 180) % 360) - 180 lats, lons = np.meshgrid(l, l2) with pytest.raises(ValueError): get_bounds_indices(snwe, lats, lons) @@ -421,10 +378,10 @@ def test_get_bounds_indices_2b() -> None: def test_get_bounds_indices_3() -> None: - """Test bounds indices""" + """Test bounds indices.""" snwe = [-10, 10, -10, 10] l = np.arange(-20, 20) - l2 = (((np.arange(160, 200) + 180) % 360) - 180) + l2 = ((np.arange(160, 200) + 180) % 360) - 180 lats, lons = np.meshgrid(l, l2) with pytest.raises(NoWeatherModelData): get_bounds_indices(snwe, lats, lons) @@ -440,36 +397,35 @@ def test_get_bounds_indices_4() -> None: assert bounds_list == (0, 4, 0, 9) -def test_hrrr_badloc(wm:hrrr=HRRR) -> None: +def test_hrrr_badloc(tmp_path: Path) -> None: """Test HRRR out of bounds.""" - wm = wm() + wm = HRRR() wm.set_latlon_bounds([-10, 10, -10, 10]) - wm.setTime(datetime.datetime(2020, 10, 1, 0, 0, 0)) + wm.setTime(dt.datetime(2020, 10, 1, 0, 0, 0)) with pytest.raises(ValueError): - wm._fetch(Path('dummy_filename')) + wm._fetch(tmp_path / 'this_file_will_not_be_made.nc') + -def test_hrrrak_dl(tmp_path: Path, wm:hrrrak=HRRRAK) -> None: +def test_hrrrak_dl(tmp_path: Path) -> None: """Test HRRR-AK.""" - wm = wm() - d = tmp_path / "files" + wm = HRRRAK() + d = tmp_path / 'files' d.mkdir() - fname = d / "hrrr_ak.nc" + fname = d / 'hrrr_ak.nc' wm.set_latlon_bounds([65, 67, -160, -150]) - wm.setTime(datetime.datetime(2020, 12, 1, 0, 0, 0)) + wm.setTime(dt.datetime(2020, 12, 1, 0, 0, 1)) wm._fetch(fname) - assert True -def test_hrrrak_dl2(tmp_path: Path, wm:hrrrak=HRRRAK) -> None: + +def test_hrrrak_dl2(tmp_path: Path) -> None: """Test the international date line crossing.""" - wm = wm() - d = tmp_path / "files" + wm = HRRRAK() + d = tmp_path / 'files' d.mkdir() - fname = d / "hrrr_ak.nc" + fname = d / 'hrrr_ak.nc' wm.set_latlon_bounds([50, 52, 179, -179]) - wm.setTime(datetime.datetime(2020, 12, 1, 0, 0, 0)) + wm.setTime(dt.datetime(2020, 12, 1, 0, 0, 2)) wm._fetch(fname) - assert True - diff --git a/tools/RAiDER/aria/prepFromGUNW.py b/tools/RAiDER/aria/prepFromGUNW.py index bca72baf8..3e2dadc84 100644 --- a/tools/RAiDER/aria/prepFromGUNW.py +++ b/tools/RAiDER/aria/prepFromGUNW.py @@ -51,7 +51,7 @@ def _get_acq_time_from_gunw_id(gunw_id: str, reference_or_secondary: str) -> dt. return cen_acq_time -def check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id: str, weather_model_name: str='hrrr') -> bool: +def check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id: str, weather_model_name: str='hrrr') -> bool: """ Determine if all the times for azimuth interpolation are available using Herbie. Note that not all 1 hour times are available within the said date @@ -66,7 +66,7 @@ def check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id: st bool Example: - check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0) + check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(S1-GUNW-A-R-106-tops-20220115_20211222-225947-00078W_00041N-PP-4be8-v3_0_0) should return True """ ref_acq_time = _get_acq_time_from_gunw_id(gunw_id, 'reference') @@ -272,7 +272,7 @@ def get_orbit_file(self) -> list[str]: # Remove ".zip" from the granule ids included in this field slcs_lst = list(map(lambda slc: slc.replace('.zip', ''), slcs_lst)) - path_orb = get_orbits_from_slc_ids(slcs_lst) + path_orb = get_orbits_from_slc_ids(slcs_lst, str(orbit_dir)) return [str(o) for o in path_orb] @@ -380,7 +380,7 @@ def main(args: CalcDelaysArgs) -> tuple[Path, float]: }, } - path_cfg = Path(f'GUNW_{GUNWObj.name}.yaml') + path_cfg = args.output_directory / f'GUNW_{GUNWObj.name}.yaml' write_yaml(raider_cfg, path_cfg) return path_cfg, GUNWObj.wavelength diff --git a/tools/RAiDER/cli/raider.py b/tools/RAiDER/cli/raider.py index 9df17f6c1..0a3eff451 100644 --- a/tools/RAiDER/cli/raider.py +++ b/tools/RAiDER/cli/raider.py @@ -318,7 +318,6 @@ def calcDelays(iargs: Optional[Sequence[str]]=None) -> list[Path]: raise DatetimeFailed(model.Model(), tt) else: continue - # log when something else happens and then continue with the next time except Exception as e: S, N, W, E = wm_bounds @@ -338,7 +337,14 @@ def calcDelays(iargs: Optional[Sequence[str]]=None) -> list[Path]: raise NoWeatherModelData('Weather model processing failed for all times') # Get the weather model file - weather_model_file = getWeatherFile(wfiles, times, t, model._Name, interp_method) + weather_model_file = getWeatherFile( + wfiles, + times, + t, + model._Name, + run_config.runtime_group.output_directory, + interp_method + ) if weather_model_file is None: continue @@ -371,7 +377,8 @@ def calcDelays(iargs: Optional[Sequence[str]]=None) -> list[Path]: # A dataset was returned by the above # Dataset returned: Cube e.g. GUNW workflow if hydro_delay is None: - out_path = Path(out_filename.replace('wet', 'tropo')) + new_filename = Path(out_filename).name.replace('wet', 'tropo') + out_path = Path(out_filename).parent / new_filename ds = wet_delay ext = out_path.suffix @@ -606,7 +613,7 @@ def calcDelaysGUNW(iargs: Optional[list[str]] = None) -> Optional[xr.Dataset]: ): gunw_id = args.file.name.replace('.nc', '') weather_model_name = identify_which_hrrr(args.file) - if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id, weather_model_name): + if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id, weather_model_name): raise NoWeatherModelData('The required HRRR data for time-grid interpolation is not available') if args.file is None: @@ -625,7 +632,7 @@ def calcDelaysGUNW(iargs: Optional[list[str]] = None) -> Optional[xr.Dataset]: ) if args.weather_model == 'HRRR' and args.interpolate_time == 'azimuth_time_grid': gunw_id = args.file.name.replace('.nc', '') - if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availablity_for_s1_azimuth_time_interpolation(gunw_id): + if not RAiDER.aria.prepFromGUNW.check_hrrr_dataset_availability_for_s1_azimuth_time_interpolation(gunw_id): print( 'The required HRRR data for time-grid interpolation is not available; returning None and not modifying GUNW dataset' ) @@ -728,6 +735,7 @@ def getWeatherFile( times: list, time: dt.datetime, model: str, + out_path: Path, interp_method: TimeInterpolationMethod='none' ) -> Optional[Path]: """Time interpolation. @@ -761,7 +769,7 @@ def getWeatherFile( elif interp_method == 'center_time': if Nmatch: # Case 3: two weather files downloaded - weather_model_file = combine_weather_files(wfiles, time, model, interp_method='center_time') + weather_model_file = combine_weather_files(wfiles, time, model, out_path, interp_method='center_time') elif Tmatch: # Case 4: Exact time is available without interpolation logger.warning('Time interpolation is not needed as exact time is available') weather_model_file = wfiles[0] @@ -775,7 +783,7 @@ def getWeatherFile( elif interp_method == 'azimuth_time_grid': if Nmatch or Tmatch: # Case 6: all files downloaded - weather_model_file = combine_weather_files(wfiles, time, model, interp_method='azimuth_time_grid') + weather_model_file = combine_weather_files(wfiles, time, model, out_path, interp_method='azimuth_time_grid') else: raise WrongNumberOfFiles(Nfiles_expected, Nfiles) @@ -789,7 +797,13 @@ def getWeatherFile( return weather_model_file -def combine_weather_files(wfiles: list[Path], time: dt.datetime, model: str, interp_method: TimeInterpolationMethod='center_time') -> Path: +def combine_weather_files( + wfiles: list[Path], + time: dt.datetime, + model: str, + out_dir: Path, + interp_method: TimeInterpolationMethod='center_time', +) -> Path: """Interpolate downloaded weather files and save to a single file.""" STYLE = {'center_time': '_timeInterp_', 'azimuth_time_grid': '_timeInterpAziGrid_'} @@ -821,7 +835,7 @@ def combine_weather_files(wfiles: list[Path], time: dt.datetime, model: str, int ds_out.attrs['Date2'] = 0 # Give the weighted combination a new file name - weather_model_file = wfiles[0].parent / ( + weather_model_file = out_dir / ( wfiles[0].name.split('_')[0] + '_' + time.strftime('%Y_%m_%dT%H_%M_%S') diff --git a/tools/RAiDER/cli/types.py b/tools/RAiDER/cli/types.py index 246fa10f5..631797d8b 100644 --- a/tools/RAiDER/cli/types.py +++ b/tools/RAiDER/cli/types.py @@ -173,8 +173,8 @@ def __init__( output_projection: str = 'EPSG:4326', cube_spacing_in_m: float = _CUBE_SPACING_IN_M, download_only: bool = False, - output_directory: str = '.', - weather_model_directory: Optional[str] = None, + output_directory: Union[Path, str] = Path.cwd(), + weather_model_directory: Optional[Union[Path, str]] = None, ): self.raster_format = raster_format self.file_format = file_format diff --git a/tools/RAiDER/cli/validators.py b/tools/RAiDER/cli/validators.py index 350de1e08..09c6fcd8a 100755 --- a/tools/RAiDER/cli/validators.py +++ b/tools/RAiDER/cli/validators.py @@ -4,7 +4,7 @@ import re import sys from pathlib import Path -from typing import Any, Optional, Union +from typing import Any, Optional, Type, Union import numpy as np import pandas as pd @@ -45,7 +45,7 @@ def parse_weather_model(weather_model_name: str, aoi: AOI) -> WeatherModel: ) # Check that the user-requested bounding box is within the weather model domain - model: WeatherModel = Model() + model = Model() model.checkValidBounds(aoi.bounds()) return model @@ -266,7 +266,7 @@ def coerce_into_date(val: Union[int, str]) -> dt.date: raise ValueError(f'Unable to coerce {val} to a date. Try %Y-%m-%d') -def get_wm_by_name(model_name: str) -> tuple[str, WeatherModel]: +def get_wm_by_name(model_name: str) -> tuple[str, Type[WeatherModel]]: """ Turn an arbitrary string into a module name. diff --git a/tools/RAiDER/gnss/downloadGNSSDelays.py b/tools/RAiDER/gnss/downloadGNSSDelays.py index afc94d1fd..7f8d6d3e5 100755 --- a/tools/RAiDER/gnss/downloadGNSSDelays.py +++ b/tools/RAiDER/gnss/downloadGNSSDelays.py @@ -8,6 +8,8 @@ import itertools import multiprocessing as mp import os +from pathlib import Path +from typing import TypedDict import pandas as pd @@ -123,7 +125,7 @@ def download_tropo_delays( # Iterate over stations and years and check or download data stat_year_tup = itertools.product(stats, years) - stat_year_tup = ((*tup, writeDir, download) for tup in stat_year_tup) + stat_year_tup = ((*tup, Path(writeDir), download) for tup in stat_year_tup) # Parallelize remote querying of station locations results = [] @@ -141,9 +143,22 @@ def download_tropo_delays( statDF.to_csv(os.path.join(writeDir, f'{gps_repo}{NEW_STATION_FILENAME}_withpaths.csv')) -def download_UNR(statID, year, writeDir='.', download=False, baseURL=_UNR_URL): +class UNRDownloadResult(TypedDict): + ID: str + year: int + path: Path + + +def download_UNR( + statID: str, + year: int, + writeDir: Path = Path.cwd(), + download: bool = False, + baseURL: str = _UNR_URL, +) -> UNRDownloadResult: """ Download a zip file containing tropospheric delays for a given station and year. + The URL format is http://geodesy.unr.edu/gps_timeseries/trop//..trop.zip Inputs: statID - 4-character station identifier @@ -155,16 +170,16 @@ def download_UNR(statID, year, writeDir='.', download=False, baseURL=_UNR_URL): URL = '{0}gps_timeseries/trop/{1}/{1}.{2}.trop.zip'.format(baseURL, statID.upper(), year) logger.debug('Currently checking station %s in %s', statID, year) if download: - saveLoc = os.path.abspath(os.path.join(writeDir, f'{statID.upper()}.{year}.trop.zip')) - filepath = download_url(URL, saveLoc) - if filepath == '': + saveLoc = (writeDir / f'{statID.upper()}.{year}.trop.zip').resolve() + success = download_url(URL, saveLoc) + if not success: raise ValueError('Year or station ID does not exist') else: - filepath = check_url(URL) - return {'ID': statID, 'year': year, 'path': filepath} + saveLoc = check_url(URL) + return {'ID': statID, 'year': year, 'path': saveLoc} -def download_url(url, save_path, chunk_size=2048): +def download_url(url: str, save_path: Path, chunk_size: int=2048) -> bool: """ Download a file from a URL. Modified from https://stackoverflow.com/questions/9419162/download-returned-zip-file-from-url. @@ -173,17 +188,17 @@ def download_url(url, save_path, chunk_size=2048): r = session.get(url, stream=True) if r.status_code == 404: - return '' + return False else: logger.debug('Beginning download of %s to %s', url, save_path) - with open(save_path, 'wb') as fd: + with save_path.open('wb') as fd: for chunk in r.iter_content(chunk_size=chunk_size): fd.write(chunk) logger.debug('Completed download of %s to %s', url, save_path) - return save_path + return True -def check_url(url): +def check_url(url: str) -> str: """ Check whether a file exists at a URL. Modified from https://stackoverflow.com/questions/9419162/download-returned-zip-file-from-url. @@ -324,7 +339,7 @@ def get_stats(bbox, long_cross_zero, out, station_file): bbox[3] = 360.0 stats, statdata = get_station_list(bbox=bbox, stationFile=station_file, writeStationFile=False) - statdata.to_csv(NEW_STATION_FILENAME + '.csv', index=False) + statdata.to_csv(os.path.join(out, NEW_STATION_FILENAME + '.csv'), index=False) return stats, statdata diff --git a/tools/RAiDER/interpolator.py b/tools/RAiDER/interpolator.py index 5d07d25fd..35644851a 100644 --- a/tools/RAiDER/interpolator.py +++ b/tools/RAiDER/interpolator.py @@ -130,7 +130,7 @@ def fillna3D(array, axis=-1, fill_value=0.0): return outmat -def interpolateDEM(dem_path: Union[Path, str], outLL: Tuple[np.ndarray, np.ndarray], method='nearest') -> np.ndarray: +def interpolateDEM(dem_path: Path, outLL: Tuple[np.ndarray, np.ndarray], method='nearest') -> np.ndarray: """Interpolate a DEM raster to a set of lat/lon query points using rioxarray. outLL will be a tuple of (lats, lons). lats/lons can either be 1D arrays or 2 @@ -151,7 +151,7 @@ def interpolateDEM(dem_path: Union[Path, str], outLL: Tuple[np.ndarray, np.ndarr return z_out -def interpolate_elevation(dem_path: Union[Path, str], x: np.ndarray, y: np.ndarray) -> np.ndarray: +def interpolate_elevation(dem_path: Path, x: np.ndarray, y: np.ndarray) -> np.ndarray: """ Interpolates elevation values from a DEM to scattered points. diff --git a/tools/RAiDER/llreader.py b/tools/RAiDER/llreader.py index bd1e2d5d4..8707071d5 100644 --- a/tools/RAiDER/llreader.py +++ b/tools/RAiDER/llreader.py @@ -8,40 +8,34 @@ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ import os from pathlib import Path -from typing import Optional, Union +from typing import Literal, Optional, Union import numpy as np +import pandas as pd import pyproj import xarray as xr - - -try: - import pandas as pd -except ImportError: - pd = None - from pyproj import CRS from RAiDER.logger import logger -from RAiDER.types import BB, RIO +from RAiDER.types import BB, RIO, CRSLike, LookDir class AOI: - """ - This instantiates a generic AOI class object. - - Attributes: - _bounding_box - S N W E bounding box - _proj - pyproj-compatible CRS - _type - Type of AOI - """ - - def __init__(self, cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: - self._output_directory = output_directory + """Instantiates a generic AOI class object.""" + _output_directory: Path + _bounding_box: Optional[BB.SNWE] + _proj: Optional[CRS] + _geotransform: Optional[RIO.GDAL] + _cube_spacing_m: Optional[float] + _type: str + + def __init__(self, cube_spacing_in_m: Optional[float]=None, output_directory: Union[Path, str]=Path.cwd()) -> None: + self._output_directory = Path(output_directory) self._bounding_box = None self._proj = CRS.from_epsg(4326) self._geotransform = None self._cube_spacing_m = cube_spacing_in_m + self._type = 'generic' def __repr__(self): @@ -59,7 +53,7 @@ def geotransform(self): def projection(self): return self._proj - def get_output_spacing(self, crs=4326): + def get_output_spacing(self, crs: CRSLike=4326): """Return the output spacing in desired units.""" output_spacing_deg = self._output_spacing if not isinstance(crs, CRS): @@ -128,7 +122,7 @@ def add_buffer(self, ll_res, digits=2) -> None: self._bounding_box = [np.round(a, digits) for a in (S, N, W, E)] - def calc_buffer_ray(self, direction, lookDir='right', incAngle=30, maxZ=80, digits=2): + def calc_buffer_ray(self, direction, lookDir: LookDir='right', incAngle=30, maxZ=80, digits=2): """ Calculate the buffer for ray tracing. This only needs to be done in the east-west direction due to satellite orbits, and only needs extended on the side closest to @@ -140,11 +134,7 @@ def calc_buffer_ray(self, direction, lookDir='right', incAngle=30, maxZ=80, digi maxZ (float) - maximum integration elevation in km """ direction = direction.lower() - # for isce object - try: - lookDir = lookDir.name.lower() - except AttributeError: - lookDir = lookDir.lower() + lookDir = lookDir.lower() assert direction in 'asc desc'.split(), f'Incorrect orbital direction: {direction}. Choose asc or desc.' assert lookDir in 'right left'.split(), f'Incorrect look direction: {lookDir}. Choose right or left.' @@ -167,7 +157,7 @@ def calc_buffer_ray(self, direction, lookDir='right', incAngle=30, maxZ=80, digi logger.warning('Bounds extend past +/- 180. Results may be incorrect.') return bounds - def set_output_directory(self, output_directory) -> None: + def set_output_directory(self, output_directory: Path) -> None: self._output_directory = output_directory def set_output_xygrid(self, dst_crs: Union[int, str]=4326) -> None: @@ -193,8 +183,17 @@ def set_output_xygrid(self, dst_crs: Union[int, str]=4326) -> None: class StationFile(AOI): """Use a .csv file containing at least Lat, Lon, and optionally Hgt_m columns.""" - - def __init__(self, station_file, demFile=None, cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: + _filename: Path + _demfile: Optional[Path] + _type: Literal['station_file'] + + def __init__( + self, + station_file: Path, + demFile: Optional[Path]=None, + cube_spacing_in_m: Optional[float]=None, + output_directory: Union[Path, str]=Path.cwd(), + ) -> None: super().__init__(cube_spacing_in_m, output_directory) self._filename = station_file self._demfile = demFile @@ -217,9 +216,9 @@ def readZ(self): from RAiDER.interpolator import interpolateDEM demFile = ( - os.path.join(self._output_directory, 'GLO30_fullres_dem.tif') - if self._demfile is None - else self._demfile + self._demfile + if self._demfile is not None + else self._output_directory / 'GLO30_fullres_dem.tif' ) download_dem( @@ -243,21 +242,36 @@ def readZ(self): class RasterRDR(AOI): """Use a 2-band raster file containing lat/lon coordinates.""" - - def __init__(self, lat_file, lon_file=None, *, hgt_file=None, dem_file=None, convention='isce', cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: + _latfile: Optional[Path] + _lonfile: Optional[Path] + _hgtfile: Optional[Path] + _demfile: Optional[Path] + _convention: str # TODO: can probably be narrowed down to a few Literals + + def __init__( + self, + lat_file: Path, + lon_file: Optional[Path]=None, + *, + hgt_file: Optional[Path]=None, + dem_file: Optional[Path]=None, + convention='isce', + cube_spacing_in_m: Optional[float]=None, + output_directory: Union[Path, str]=Path.cwd(), + ) -> None: super().__init__(cube_spacing_in_m, output_directory) self._type = 'radar_rasters' self._latfile = lat_file self._lonfile = lon_file - if (self._latfile is None) and (self._lonfile is None): + if self._latfile is None and self._lonfile is None: raise ValueError('You need to specify a 2-band file or two single-band files') - if not os.path.exists(self._latfile): + if not self._latfile.exists(): raise ValueError(f'{self._latfile} cannot be found!') try: - bpg = bounds_from_latlon_rasters(lat_file, lon_file) + bpg = bounds_from_latlon_rasters(str(lat_file), str(lon_file)) self._bounding_box, self._proj, self._geotransform = bpg except Exception as e: raise ValueError(f'Could not read lat/lon rasters: {e}') @@ -270,12 +284,12 @@ def __init__(self, lat_file, lon_file=None, *, hgt_file=None, dem_file=None, con def readLL(self) -> tuple[np.ndarray, Optional[np.ndarray]]: # allow for 2-band lat/lon raster from RAiDER.utilFcns import rio_open - lats, _ = rio_open(Path(self._latfile)) + lats, _ = rio_open(self._latfile) if self._lonfile is None: return lats, None else: - lons, _ = rio_open(Path(self._lonfile)) + lons, _ = rio_open(self._lonfile) return lats, lons def readZ(self) -> np.ndarray: @@ -292,15 +306,15 @@ def readZ(self) -> np.ndarray: from RAiDER.interpolator import interpolateDEM demFile = ( - os.path.join(self._output_directory, 'GLO30_fullres_dem.tif') - if self._demfile is None - else self._demfile + self._demfile + if self._demfile is not None + else self._output_directory / 'GLO30_fullres_dem.tif' ) download_dem( self._bounding_box, writeDEM=True, - dem_path=Path(demFile), + dem_path=demFile, ) z_out = interpolateDEM(demFile, self.readLL()) @@ -309,8 +323,14 @@ def readZ(self) -> np.ndarray: class BoundingBox(AOI): """Parse a bounding box AOI.""" - - def __init__(self, bbox, cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: + _type: Literal['bounding_box'] + + def __init__( + self, + bbox: Optional[BB.SNWE], + cube_spacing_in_m: Optional[float]=None, + output_directory: Union[Path, str]=Path.cwd(), + ) -> None: super().__init__(cube_spacing_in_m, output_directory) self._bounding_box = bbox self._type = 'bounding_box' @@ -320,10 +340,15 @@ class GeocodedFile(AOI): """Parse a Geocoded file for coordinates.""" p: RIO.Profile - _bounding_box: BB.SNWE _is_dem: bool - def __init__(self, path: Path, is_dem=False, cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: + def __init__( + self, + path: Path, + is_dem: bool=False, + cube_spacing_in_m: Optional[float]=None, + output_directory: Union[Path, str]=Path.cwd(), + ) -> None: super().__init__(cube_spacing_in_m, output_directory) from RAiDER.utilFcns import rio_extents, rio_profile, rio_stats @@ -355,7 +380,7 @@ def readZ(self): from RAiDER.dem import download_dem from RAiDER.interpolator import interpolateDEM - demFile = self._filename if self._is_dem else 'GLO30_fullres_dem.tif' + demFile = self._filename if self._is_dem else Path('GLO30_fullres_dem.tif') bbox = self._bounding_box _, _ = download_dem(bbox, writeDEM=True, dem_path=Path(demFile)) z_out = interpolateDEM(demFile, self.readLL()) @@ -365,8 +390,10 @@ def readZ(self): class Geocube(AOI): """Pull lat/lon/height from a georeferenced data cube.""" + path: Path + _type: Literal['Geocube'] - def __init__(self, path_cube, cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: + def __init__(self, path_cube: Path, cube_spacing_in_m: Optional[float]=None, output_directory=os.getcwd()) -> None: from RAiDER.utilFcns import rio_stats super().__init__(cube_spacing_in_m, output_directory) self.path = path_cube diff --git a/tools/RAiDER/losreader.py b/tools/RAiDER/losreader.py index 7d5e44f13..ba3ee035b 100644 --- a/tools/RAiDER/losreader.py +++ b/tools/RAiDER/losreader.py @@ -173,9 +173,6 @@ class Raytracing(LOS): def __init__(self, filename=None, los_convention='isce', time=None, look_dir='right', pad=600) -> None: """Read in and parse a statevector file.""" - if isce is None: - raise ImportError('isce3 is required for this class. Use conda to install isce3`') - super().__init__() self._ray_trace = True self._file = filename @@ -208,8 +205,8 @@ def getSensorDirection(self) -> Literal['desc', 'asc']: end = np.argmax(t) return 'desc' if z[start] > z[end] else 'asc' - def getLookDirection(self): - return self._look_dir + def getLookDirection(self) -> Literal['left', 'right']: + return 'right' if self._look_dir == isce.core.LookSide.Right else 'left' # Called in checkArgs def setTime(self, time, pad=600) -> None: @@ -218,9 +215,6 @@ def setTime(self, time, pad=600) -> None: def getLookVectors(self, ht, llh, xyz, yy): """Calculate look vectors for raytracing.""" - if isce is None: - raise ImportError('isce3 is required for this method. Use conda to install isce3`') - # TODO - Modify when isce3 vectorization is available los = np.full(yy.shape + (3,), np.nan) llh = llh.copy() diff --git a/tools/RAiDER/models/credentials.py b/tools/RAiDER/models/credentials.py index 162a1f176..12bbab645 100644 --- a/tools/RAiDER/models/credentials.py +++ b/tools/RAiDER/models/credentials.py @@ -87,7 +87,7 @@ def check_api( model: str, uid: Optional[str] = None, key: Optional[str] = None, - output_dir: str = '~/', + output_dir: Path = Path.home(), update_rc_file: bool = False, ) -> None: # Weather model API RC filename @@ -101,7 +101,7 @@ def check_api( # Get the target rc file's path hidden_ext = '_' if system() == 'Windows' else '.' - rc_path = Path(output_dir) / (hidden_ext + rc_filename) + rc_path = output_dir / (hidden_ext + rc_filename) rc_path = rc_path.expanduser() # If the RC file doesn't exist, then create it.