diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 381ce013d..f134759d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -313,6 +313,51 @@ jobs: name: coverage-report path: coverage.xml + # run unit tests against older pandas versions, since the test job installs only the newest pandas in the supported range + test-pandas-versions: + needs: [pre-commit] + timeout-minutes: 60 + + strategy: + fail-fast: false + matrix: + include: + # pandas < 2.2.2 is not compatible with numpy 2, so those entries pin numpy 1.26.4 (the last 1.x release) + # pandas 1.5 is the lower bound in setup.cfg and installs only on Python 3.11 + - python: '3.11' + pandas: 'pandas==1.5.3' + numpy: 'numpy==1.26.4' + # pandas 2.1 is the first version with DataFrame.map and the first to support Python 3.12 + - python: '3.12' + pandas: 'pandas==2.1.4' + numpy: 'numpy==1.26.4' + # pandas 2.2.3 is the first version to support Python 3.13 + - python: '3.13' + pandas: 'pandas==2.2.3' + numpy: 'numpy==2.5.4' + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install py-dependencies + shell: bash + run: | + python -m pip install --upgrade pip + + pip install -e ".[boto3,pandas,pysftp,tests,curator]" "${{ matrix.pandas }}" "${{ matrix.numpy }}" + + - name: run-unit-tests + shell: bash + run: | + python -c "import numpy, pandas; print(f'pandas {pandas.__version__}, numpy {numpy.__version__}')" + pytest -sv tests/unit + sonarcloud: needs: [test] if: ${{ always() && !cancelled()}} @@ -350,7 +395,7 @@ jobs: # on a GitHub release, build the pip package and upload it as a GitHub release asset package: - needs: [test, pre-commit] + needs: [test, test-pandas-versions, pre-commit] runs-on: ubuntu-22.04 diff --git a/CLAUDE.md b/CLAUDE.md index 3155b0df9..a19452b19 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ Synapse Python Client — official Python SDK and CLI for Synapse (synapse.org), - Python 3.11–3.14 (`setup.cfg`: `python_requires = >=3.11, <3.15`) - HTTP: httpx (async), requests (sync/legacy) - Models: stdlib dataclasses (NOT Pydantic) +- pandas (optional): `pandas>=1.5,<4.0` (`setup.cfg`, 4 places) - Tests: pytest 9.0.3, pytest-asyncio, pytest-socket, pytest-xdist - Docs: MkDocs with Material theme, mkdocstrings - Linting: ruff, black (line-length 88), isort (profile=black), bandit @@ -107,6 +108,12 @@ pandas, boto3, pysftp, and other optional extras must never be imported at the t For type annotations referencing pandas types, use `DATA_FRAME_TYPE` and `SERIES_TYPE` from `core/typing_utils.py` rather than `pd.DataFrame` or `pd.Series`. These aliases resolve correctly at type-check time without requiring pandas at runtime. +### pandas version compatibility +Code must work on every pandas version from 1.5 to 3.x. Use one code path that works on all supported versions. Use a feature check only when no such code path exists. Known pandas 3 traps: +- Text columns have the `str` dtype, not `object`. Use `pd.api.types.is_string_dtype()` instead of comparing to `object`. +- `DataFrame.applymap` is replaced by `DataFrame.map`, but `DataFrame.map` is not in pandas < 2.1. Use `df.apply(lambda col: col.map(func))` instead. +- `iterrows()` gives a `str`-dtype row when all values in the row are strings or missing, and `None` becomes `NaN`. Check each value, not only the column dtype. + ## Constraints - Do not use Pydantic for models — the codebase uses stdlib dataclasses with custom serialization. Mixing would break the `@async_to_sync` decorator and `fill_from_dict()` pattern. @@ -126,6 +133,7 @@ For type annotations referencing pandas types, use `DATA_FRAME_TYPE` and `SERIES - Integration fixtures create per-worker Synapse projects; use `schedule_for_cleanup()` for teardown - Auth env vars: `SYNAPSE_AUTH_TOKEN` (bearer token), `SYNAPSE_PROFILE` (config file profile, default: `"default"`), `SYNAPSE_TOKEN_AWS_SSM_PARAMETER_NAME` (AWS SSM path) - CI runs integration tests only on Python 3.11 and 3.14 (oldest + newest) to limit Synapse server load +- CI job `test-pandas-versions` (`.github/workflows/build.yml`) runs the unit tests on older pandas versions: 1.5.3 on Python 3.11, 2.1.4 on Python 3.12, 2.2.3 on Python 3.13 (ubuntu only). The `package` job needs it. pandas < 2.2.2 requires `numpy<2`, so each matrix entry pins its own numpy version (1.26.4, 1.26.4, 2.5.4). ## Maintenance diff --git a/Pipfile.lock b/Pipfile.lock index b38f178bc..5164fb2fb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -455,118 +455,120 @@ }, "opentelemetry-api": { "hashes": [ - "sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7", - "sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3" + "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", + "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-http-transport": { "extras": [ + "requests", + "requests", "urllib3" ], "hashes": [ - "sha256:2c229b6593eaa22c86d9b8a15843dc23b406dbda00fb138339189aab07923b4e", - "sha256:c82689928a11505a2a0a26a04afe0ce06d270d3db984bc16c1a80ffa902db00e" + "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", + "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-otlp-common": { "hashes": [ - "sha256:35d24c867310f4713a9738b0202b6bade77955418fccb9e2f7eea01b76263916", - "sha256:362268ec6aa705e183776ff938539df1e8ce45bc5509d242538b1d40c26fe6a6" + "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", + "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-otlp-proto-common": { "hashes": [ - "sha256:36495115a0c6a7aa946cfda9d59b6ed4e917b6ab0f75cdaf66bc1b176ec1be1f", - "sha256:7e1410ae6d3ed301f7a74bec96467d519d3f37e52f9d95b309ae125e5a1af863" + "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", + "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-otlp-proto-http": { "hashes": [ - "sha256:2f35496d96809f946f41b8805e6b93aec6c9b71b5fd759b75b6af4c084d992ae", - "sha256:74385f99266dafdefff41f3da4b76f6dba79d237a9d4487e6ff1ee653c74a0a3" + "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", + "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-instrumentation": { "hashes": [ - "sha256:0b32e2fcfec1e4bd48511a3b77ce32bfb1c16dcd5479396b21f9086a4b06c0c9", - "sha256:316715ed7b33a12530c0ae3e8d4cdf26d70e1aad358d7ba256e5dea2d4ceed71" + "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", + "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-httpx": { "hashes": [ - "sha256:2735d1868749ca04ce47a3802b0b809e3a00c05a65140e05bac5700e74410539", - "sha256:c89297975f54d67692d3c29e9e33a1bdac47710031cc3ed0d83a0c9765336981" + "sha256:0342a4002c6dbc6c4bf22cc7e698f50f5c8b77f63325c6f40c94ab87e016bf4d", + "sha256:5865a72c68098c85955a271ab8744b480a36e3ee492d35b8cadb93c7c4dbb618" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-requests": { "hashes": [ - "sha256:053dde08fbc1d223058e33d477cf81e2bdcbe7af3893787283339b2b557d4b7a", - "sha256:2f9df08bf99f5da4bcfda70735492b4364bfcc5bd3f48232658e936f97ee1a22" + "sha256:28578f72e68e3a5be3226c618ac9570ed360ef1dd22d5d6e0721c6905a4ccc67", + "sha256:7ba17d984a2bd876b88bf0aafcca27b5e3ff4c1821e0fe07641e380a3cf3a3a2" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-threading": { "hashes": [ - "sha256:4aaf38b96b47a04319b8d96bee4f1daa04e0d6c697525595746aeb61618c002d", - "sha256:4c1c9adf42b83b6f44ca4bb99799e066ccfb1869c2029bc73d862c40a8ef932e" + "sha256:2c217359439d09ca4a4be524db368c05a3cc7f218b85e8086f22664db0a03538", + "sha256:94039c60603060d26524e31aed1181a5d153e43f58462d110dbeb4830687d7d7" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-urllib": { "hashes": [ - "sha256:3eb6d2b9b28bb51188d77092a58eac2ab04852d244acb51333bdf42b0e933c68", - "sha256:a919d8014dcf0065ad42ce5c78dd158c76c7342929f451d726462f7ffa68e367" + "sha256:6f679d785ebcb04b791a889d8a9c0e8edf897a85ab80a7a12b0139c461b14627", + "sha256:f00daa25375fbdf88b7e1a425ef54c704674d8bef639c834de93df9b3f88d7f4" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-proto": { "hashes": [ - "sha256:96ee414f24bc3f61ea8e17dc56b4348d4049d73db3eb17c6b3edf75b5b403300", - "sha256:9731566359d7b8e1ee1e149d8e4ad6865bab965e657ad3387ec2784d16927666" + "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", + "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-sdk": { "hashes": [ - "sha256:20caa5130505e386c67c3da1c76e446c842698ced54c76c6148679539aa97972", - "sha256:5dc634c946546f61b757c5b1781f9fd1a10e96ffaf7357c797e508fe9c57e75e" + "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", + "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-semantic-conventions": { "hashes": [ - "sha256:175b19dd98c4473f4f43a2b1df59186fd7b4a48cd3f77cbe03438b6d6fda230a", - "sha256:97a77dce484c54861e7eeff7651fd8a806dd3c30e501dc316730215ec36890e6" + "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", + "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-util-http": { "hashes": [ - "sha256:7f5e581e94f023b30995f700e41ce319cec82bd542ff1c1c7c9b56d6b10bb687", - "sha256:a2d53859630abe8e5abcc4f867c5a809bb95873033573b1a67f6ef43b57a8627" + "sha256:047dea1a628031f857a5a32261dc0e955bc162d39993ed1cffb8f2cff5ba8a62", + "sha256:8f443d7abcaf29c4a07b373bbd31b5b39132c0ed3c27d015a59dc0323d5b1c58" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "packaging": { "hashes": [ @@ -619,11 +621,11 @@ }, "pycparser": { "hashes": [ - "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", - "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" + "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", + "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc" ], "markers": "implementation_name != 'PyPy'", - "version": "==3.0" + "version": "==3.11" }, "requests": { "hashes": [ @@ -820,15 +822,16 @@ }, "backrefs": { "hashes": [ - "sha256:4a627b817fd2dce43b79ab48da63613340509381cd8ce0897078a0bce79a2ab8", - "sha256:601ce68ca12385dbda06ce264406b4c4210cf5b79fd0fd627592365c92f29a88", - "sha256:87f0fae8c5f207fe9f4b2887efc71d42f4900ac78faa1af08d675ef303692dc5", - "sha256:9ec96efa080938be92323e8e730e57718c9c88eb15ad70bbef4e1766df591408", - "sha256:b556cd7d36c3a3a2f256b89590b176b8eddfb73bcfaee3a3ddd84ea66d21ce50", - "sha256:f0c35cf0102ba6b6070c12a492be3c1c1d3f5839529784b9a9565d6d04569a01" + "sha256:11f61846e446fa8637af2f5c073f1083aa58dbb7460b019fd51689f564e44f7e", + "sha256:1e857c32e91b8fe46ddb359ac21021a37b090fa4239566a61e9856123e334041", + "sha256:438bd38dc7fa0cded52786a1351a2dad9efc2cda95d047284c41d916a22312a8", + "sha256:9c8fe3e2eaae4301a9ce787341d986e5ba25a887d89da6de0de5d58eca26a949", + "sha256:b472c76082769f86a97ffccfd1f2599b2b706610c6154955664fffe67e909035", + "sha256:db577dbae1c61187534d35fc9bfbd0c01158fff6239a66fecf7590e5b17879a9", + "sha256:f86d9312df3ebae1241758af80809a319cafa1d8f32189edfb242c9aa55573b6" ], "markers": "python_version >= '3.10'", - "version": "==8.0" + "version": "==8.1" }, "bcrypt": { "hashes": [ @@ -901,52 +904,52 @@ }, "black": { "hashes": [ - "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", - "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", - "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", - "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", - "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", - "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", - "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", - "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", - "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", - "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", - "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", - "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", - "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", - "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", - "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", - "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", - "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", - "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", - "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", - "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", - "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", - "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", - "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", - "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", - "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", - "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", - "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae" + "sha256:03c0ddd93bb392e71209903a691767eb366fe1a76deb9509ccbaae9e1f14bb52", + "sha256:0ce08b367307b0fd91c9dd1d4084e62b05b3055475f951f0f34a46b6e2393b64", + "sha256:182f6c32be38074b16d378498c498b32cb51928178ee611485344972c35ec9c6", + "sha256:1935b32f5326028019856e18cb42b4da63db23765dc84464cec723e0de478a9b", + "sha256:19fa8f5beb5e77c54c9c7e21d00cc93ed6c8b6228ee385616906d6befe081143", + "sha256:2520037aa62f8a1454d0811b8f5c88b444445b03a4bfba480d8d220893b64c34", + "sha256:28842f9a8207cc1df6eb983a35a14c5a0dfcd603d214fe82d84bef552afd2e3a", + "sha256:289282aa2e09d3162312a3be1788ff21b08e9ea9cc4a81e656024728b32428fb", + "sha256:2ffbc023a12d0c729408823b8f10514490bd0baa301d0d4e21a7240249f9507f", + "sha256:3414a0c52901964dceabd98c7c56beac0f964115a116ecedcce7247359b14017", + "sha256:4d9a90516db1d99c25dbb20cc0998e0e01531dd903466c7744e56d66f864220a", + "sha256:51d5e417e700fe6ec0b0ecdc408c6f6cb5def80328f31f724993d82c6486b746", + "sha256:5cd88fd7b444ca51f3fc883b6f6657ea53a258b0b2eef6d9f2dfcfa17ce0e27b", + "sha256:5f9f83beae62437e060dafd53d7f1fc327e3d3494f74d72ee5c2b73eb90fc4e7", + "sha256:70ccbd175b7f6be29d2b727ee7ca6b4c54053df59da653a6df80b175d20a94fa", + "sha256:7bdade400bfe24d78a7762896acc2f9a8e1a17fb0fd0536bf6b7c7097cf3eec7", + "sha256:8375962579d537364cc0efa19b1474481915d3a793f9fc0774901814c5e5b5f4", + "sha256:978113a40223a6aaefc17364176a809a320e6b288683841427fff04c6d7b4130", + "sha256:9a0219b29cd70e49f920acb7081e6ce5025c719008447c521d0200dcad93206a", + "sha256:b5347d760f0c02bb00dd249384cab71c3bf828b4f68d5b401eb116e0390f147d", + "sha256:b6272cfd7e1e8e271f5b0e0207259fe2834687e5cb9b5f620b34a44db9754993", + "sha256:d42dd2fac7c342ae67e64ee99c9532e20b2a84e92c79ed3317fa2ef54c801d93", + "sha256:d5bd3518d8e97138fef295230b1e9804076d69fa4e3594071494a8c68abe6266", + "sha256:d8b3a9074a680b3c5749633714e9ae3992a1e5a23343a97ad61cd9b119b444d2", + "sha256:f6dba8138cdc99061ef07b958ac082d2aa057b6961d1936f9717c350f02bab5f", + "sha256:fe85fc4019bee59bc495c0f2a8ee76c5cd02c7015508d94a967ba2376f39a52c", + "sha256:ff57f63029aa1353fa8b1b0c8971fd88a6c92dc766608d2eee33ad2deb23270e" ], "markers": "python_version >= '3.10'", - "version": "==26.5.1" + "version": "==26.10.1" }, "boto3": { "hashes": [ - "sha256:632a4f8298725b94144c8cd383cbdacb34624daa73221e954c3d33ac6e2528d6", - "sha256:c4e0f1a0295cbb7103f2950128cf88463c076d220080a7d0f127cf834969fc3a" + "sha256:5ae342a16c848909cd42d4be404f69d9082e5705460198d4d3327eca5f6cddcb", + "sha256:c79994619c8d89e45f6fd0edc5c5b5a70c9358f00423f4c99cb64931f89ecf37" ], "markers": "python_version >= '3.10'", - "version": "==1.43.107" + "version": "==1.43.111" }, "botocore": { "hashes": [ - "sha256:23cbe854e815dbaccf097f7fd32b461c9e1d2ed7e0c7dcc5658218704509d840", - "sha256:4a37fa072a00280c746313532d19b00e2dc53f1993222601df104d71d548d5b6" + "sha256:44d5e80962ac6cb9e85af72667b77c9586451e3328ab0ce33195380767e213d8", + "sha256:f1f4c28cb2a096bf246d0bb24cbb1a01c5cb696ef499fa71b155adda7b94c90b" ], "markers": "python_version >= '3.10'", - "version": "==1.43.107" + "version": "==1.43.111" }, "certifi": { "hashes": [ @@ -1475,14 +1478,6 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.3.1" }, - "dill": { - "hashes": [ - "sha256:1e1ce33e978ae97fcfcff5638477032b801c46c7c65cf717f95fbc2248f79a9d", - "sha256:423092df4182177d4d8ba8290c8a5b640c66ab35ec7da59ccfa00f6fa3eea5fa" - ], - "markers": "python_version >= '3.9'", - "version": "==0.4.1" - }, "distlib": { "hashes": [ "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", @@ -1500,11 +1495,11 @@ }, "filelock": { "hashes": [ - "sha256:635e7d67fa92654eed444e75e9ca18426d34e77ad9c469bf4373f75a932f7b22", - "sha256:9287fd61b99a806e5202be29a83034c0808a1b9830e820537c2f5773981f7eeb" + "sha256:3f4a557945a7b0f95efeb1f432267affe5d45ac8ddde2aed1b97ebb62382c089", + "sha256:7ba0927482c5a814b0a7f391d029ccdb8010f576f0a74c0dcde1811e8bc4c1b6" ], "markers": "python_version >= '3.10'", - "version": "==4.0.9" + "version": "==4.1.1" }, "flake8": { "hashes": [ @@ -1537,11 +1532,11 @@ }, "griffelib": { "hashes": [ - "sha256:1b8f9cd525681c26b1d6d574faa1371651e8459ca51d209684f50b8096ae06e0", - "sha256:7b0952caf5bca6afa4bb5ee8c6a2d183fe3f21b62efc5f6c7243cb2b26d2d115" + "sha256:8e710afededd5607f95bf3c8ccc175ee306e7084459faf27b9f9f44ef2a7a274", + "sha256:df00c7a0dee3d86268d76788997a1859272cb1fb7b865658e043d2c0c3d52e60" ], - "markers": "python_version >= '3.10'", - "version": "==2.3.0" + "markers": "python_version >= '3.11'", + "version": "==2.3.2" }, "h11": { "hashes": [ @@ -1593,11 +1588,11 @@ }, "iniconfig": { "hashes": [ - "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", - "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", + "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7" ], "markers": "python_version >= '3.10'", - "version": "==2.3.0" + "version": "==2.3.1" }, "jinja2": { "hashes": [ @@ -1649,98 +1644,160 @@ }, "markupsafe": { "hashes": [ - "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", - "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", - "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", - "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", - "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", - "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", - "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", - "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", - "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", - "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", - "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", - "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", - "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", - "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", - "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", - "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", - "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", - "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", - "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", - "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", - "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", - "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", - "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", - "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", - "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", - "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", - "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", - "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", - "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", - "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", - "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", - "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", - "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", - "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", - "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", - "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", - "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", - "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", - "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", - "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", - "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", - "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", - "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", - "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", - "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", - "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", - "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", - "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", - "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", - "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", - "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", - "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", - "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", - "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", - "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", - "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", - "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", - "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", - "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", - "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", - "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", - "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", - "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", - "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", - "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", - "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", - "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", - "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", - "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", - "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", - "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", - "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", - "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", - "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", - "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", - "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", - "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", - "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", - "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", - "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", - "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", - "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", - "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", - "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", - "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", - "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", - "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", - "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", - "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" + "sha256:007e1ffd9bf65bb6ee96df7b258fc632a4868dd5566037986c64781f35a36e98", + "sha256:02fa4acbc6a3fc5c693c34d4dd8c1130b7fe99cc915181b0ddd6f72aeb296002", + "sha256:03470d1a8268e692ecf79ecd565593e59d44219377a7ead61f1f1b94c1f7ff6b", + "sha256:04e7902ba80ee4bac1d50a549606527a1dcf0476cd81403db41099d3b60ec653", + "sha256:051417f74bcaaefa316276e0ff723f541616ca51043d070da00249d9bddd3e3c", + "sha256:05295589e619b9bed252a86b532b8e27350abc372d18ba89b59375325e91ec1e", + "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", + "sha256:0764a13d34cae40db7bbf3a09b7e9b491bf4603e20b263a7a9d6b8e324975d0a", + "sha256:077293e425f28ec737dbcad442a71752e28f8ae27cde3d68acd1fb212091cd92", + "sha256:0930db9bdc62d22944e10b066448bb65dc9abe9112880c7cab8da54db4284d5f", + "sha256:0cee7cb0f9a1b6892ea482237d9403b3d1b4603aee057d0ff01f0fac2d019a97", + "sha256:0d9c47709875fdb321452056622e930c52afbc07a7d780762fbb8b4d91ce6fa4", + "sha256:11935df9bf455ed0c04eb87bcd720f02b1fe5e02128a9430f23aed6f93336fc7", + "sha256:12a606a492de952afcb43b59a14aaaaad120e708d3663dd0fdf2d738d427a691", + "sha256:14bd2d845d62ab678eaf81da89d7b621b51756c72346745c1a594c09d49207a2", + "sha256:15ba9e28640feef770374b116a6f019c21f52404aeabe516aa7f800587b98cfc", + "sha256:18a801868a884f216e784d7d14db2a4077143ce7610440aee2ce8f734e7cfcde", + "sha256:1c0df495a977d10460a94941799c72d5b5ab03d3858d949b55b5a66c8f371c99", + "sha256:1caa2fa5a6184fb233153b35f654e6687bd555476f6170f29d8ee9be1a8b0af9", + "sha256:1e1451fab512d1bcc3dc26988ec1edb0b82c2db909132872cd9356070a6b63df", + "sha256:1f1f9477e174582b0a1b583d60b66e1f2cf5d3fe12cee985e4aedf44766600e5", + "sha256:2628d3a8cb648ecebb3c5d6b0a1052d400e4d8b7ac0fb786be8d285b50040d17", + "sha256:26e9867520db70d37f7fb421a7f0d8adb40171011fb84ce869afa1a83370dfa8", + "sha256:2a6ef68ae94aed8721934072b27a3b654ea2100b97e4ab864cf1489c90926fbc", + "sha256:2b2b1e18af909b448bb3cf9e3433366f7a8726271fc214e8b10e0f62a78c724b", + "sha256:2cb3dd71fc6be918ad4264346a8ed69485f9b7ed7bf35495d8e22807cd6b8bea", + "sha256:2d1b7d9308288661f56672b1b157d75fc536714d3638487bbea17b6318a78248", + "sha256:2dad610540cb2e6272855c178f08ae9a1c7ac258a7fb71660553a5f104b42741", + "sha256:2e5a7cd7fdd14fcb1ae5d7d8bf23d24fbd1daefd1fbca2580132e1ea75f098b5", + "sha256:2e9ad7dd851bf45fab9f75cbff4cb493fee9979e8d8c7c9c3ee119022518edd6", + "sha256:340cbb1957ba99929cbf19a75626d36ba1ae21d1730b287d1cf7f824a20c4fc7", + "sha256:34bdde374c5932765d7dc685c4a1d191a3207852d67e8e0a9eb6ea85156181f1", + "sha256:353bd63081912ab8cfa6a0c7d185934cdf8426f04c618bba6bc4b394f2069b67", + "sha256:387d8cd30e69b3f0a72877b9ae717033396404e19095b17fe89753a981fda44f", + "sha256:3882fb412298575bae3b9c46868251f15cc69307359f87bb1b382e53d6e5a2c9", + "sha256:38fc55594dab834470b6733dead2ee9e3f657fb0608c769dcafa0ba5ab52f45c", + "sha256:396ec4e65cc889f69786b3b89478b471cee5a3bcf468b9d9bb03e1a30fb291fc", + "sha256:39dbacefc411633db5b4378b066a9aca70a3d7e2922c9e578d825f844026eeba", + "sha256:3a93d9616ddecfb393727a0041a562cf0b15a244e20f2bd25efc7949be4c4f17", + "sha256:3d23795802fc8bd72534836d64489bbf0f67c088959091bdb22e10735a5107bf", + "sha256:434139499bb20b502ed3baa1f169e618f924a97e7a777fea1a49446d80106cf6", + "sha256:436e3ffc6310d3c41878c601db29098102fe5d8a467c49da4a4125254e0980f2", + "sha256:489505b03f692c3f376394e49194fa7a7f9e8558d6e293a7056a0032b0c38163", + "sha256:4a540e2d3192792fc84eced57bef37851ccb2b41f73291bb17408eea77bcd278", + "sha256:4a7cdc2a420ca01058182da4253329764d4bfa055564d1eced90e6ba1e8b1d3d", + "sha256:4bced6e2a6dba6a28f7dd3c6ce14df1b2dd495923f16ea484cad03decd463b2b", + "sha256:4cf3468d5ec187ffffcaca8e61929a37448f215dafc1386a12c750a72fe53634", + "sha256:4e2c4809c14559aa7ef426f27fb35afbb38104c349a903bf8f3600456764bb38", + "sha256:4ed644d75aa94a2baf7ec3a96eaa160ea58c742eb9d27c6506053c5c40fc84ed", + "sha256:4f6e0852a0283b1b1fd776eeb7b766a5f440b3e2bd31ab51af3b400585f3965c", + "sha256:5066b244f576f91afc8ee3ba029a89f99d39c79b1853fe9d39bea9f0afbec148", + "sha256:5086f9975abb1ab531ee6afca1761e4b59a19b446f3f6522ed776963228cfe5a", + "sha256:50b5bedc9ed8a94fc8857a42ef4f84a81ea88f8d4f05dc8705fb23ee6d8dcca7", + "sha256:52704c5d36eb6dda8866493decd61111fff86244c9b1ad225ca01b9e91e5970f", + "sha256:55ffd6ce583d97dc71dc92e930324c8c0d25aea7e3ade6ae54ef77cedb096811", + "sha256:569d65055d367e3dcdf30c3f41119467b73d9ee9faf332bdf40402644f5ac08e", + "sha256:57f9947a7e57a081c1e3e0a2dd0d2dcf290a4531450e6f611e30084c222a7295", + "sha256:5989cb26b2e1efc6a42216a9f6b5ee495ce5ace2e5b352a9af489976b32d1ee2", + "sha256:5c22873ad1f0532ba40fa1727f3c0fc1bbbaab6d373d4cbe3f0dc74b2e2521c7", + "sha256:5e8b3d0b18fd623afa12ecb2ce8d8becef69f9b5440c6330c7972200e0bb84b0", + "sha256:61631e08084be9e21a8967ec3139c7616ed7c5e9368e05c86d1b39562c8a57b6", + "sha256:64511c54db4e4987aef4c41923235927428729e8174c5dba488429be70a998ed", + "sha256:6669c1bf34080161ce49c589cc512ef24d4c704ac9d2b2d3667f519c60418378", + "sha256:672d207103e6b16ca098611b0f9efad6bc00afd47c03d6ef62186495ca677dc0", + "sha256:6768d67d1bce64270e0fdc2e69309d68b9b18ae56ddf6c711d168e9d051c2cac", + "sha256:6a45c3d514f2436064db00d7fc8778d888f0236ebfed649b53d13a59e69ad51b", + "sha256:6bd9e1788e15bfcf6a9082de42e30387e7b85d211ab21e57a939bb8cfaaf8d96", + "sha256:6d2a9efe686f9de00d0d1ea32a4a5a86d558a2277501bd78d964214eab625e59", + "sha256:6da83a088f8ef93b2d483a8232a4dbf4d69d3d8496b568a03c56becac43e1808", + "sha256:7018d4af1cd272e847aa5917983ab5e83e4f6579f9dbfecd4a79c0ca80b144c2", + "sha256:71f88e749ea29f67f21f3b36433c1dc54c7729ed2a6d9e2da2e0d9e0d7b224eb", + "sha256:737c9c3981998eba27f11786f84fddcbabc74068b72a4a1f454ea02094b57b65", + "sha256:73e77980c7207854f00fc4e71fb1626868d5740ab4012623d55c7a99ad122a72", + "sha256:799c39bdf5e2f1292fedd3009f7b3c9e760f10b2420cb9638d56920840ff6db8", + "sha256:7a83aa6e4805df46fed18e989d3d16f86ef60cb50bbc8d9ce3a6be89165fbf6e", + "sha256:7d3391b2188d18737cb2fa147028b1096236eaa7e156446c650a489fa2cadc91", + "sha256:7e1636da3d8dfc220b6dd10264db5f2b165e4888c4518594898fbe381049af8a", + "sha256:805c8b84534fa10891890f0e4be39f3a99e94615d93e8836bf9fa1fdca2feeb2", + "sha256:811d02d5122171c1941357efd8f9bf4ffe907b7f0a1a4e729a880e4be3f46e3e", + "sha256:8138eb83940ec7299024d92d4dee45f601b9e6c5ffde9d25f4e35e326203c707", + "sha256:83b3944fea42a8400edf92fd1770fb8d0d4f7de651353bd2d8525a92dba69a21", + "sha256:849dd2bb0e5e4ab2b71c7191726a4a8d5aa8a610daa584728cbee0b710ddc4ef", + "sha256:8698d70a8081ee8c090dbb394768b5789a1da8b131b5499f89d071dd3cfaf6be", + "sha256:8781a792a070cf2bd1b86d3aa943894115faaba6e88122a7bf32d62072742453", + "sha256:88d59b473bfb03259722600839af9bbd7fa13a2eb514beefeedb95997882f69a", + "sha256:8909c2f1c6dd65e054ac4b573a91c8384d1492281e55d82d159d653f7a13adf6", + "sha256:8965520ac587c94a4ac48b729be3d8b8de00af39699b17585dfb599babe77977", + "sha256:8b5d563170ff8ba3181caa967c99a3c804d1dedb702c7cb93a6a7c32247da978", + "sha256:8e124f974786f831d6043728e38296969d3579db8896fe004682f5758e613581", + "sha256:8f0fac8b13d14bb06c68195f849371924ae53dd7b1c00fed24650f704383b692", + "sha256:9240187afb63d2f9ddc3e032c670356fe941f6e20662ea168a5dc3f1f317e1b3", + "sha256:925f929d6b59a8b3f8b8c6ac363cd0af7eecc81efb3071770b3c6717c450a369", + "sha256:9348cbb300d224fe3b89793262cb093504d4ae927004468463f745188a193e4a", + "sha256:9388003072b95f2f1e3fd908604194d653ba21330d811961a78b7da1a77e9e36", + "sha256:9438a2648b2195980cb2dd8e53ed7b8df91319e2d0b70ae61a9e1d1bc8d3bec9", + "sha256:94e4c421742086aeee4c32a506eec8859d7634aad943f7e6aacf70f813478768", + "sha256:94f5407f7bc64fa6463906b896f9904beeeb7dd8dc116ee8e9056c8714ff9916", + "sha256:971a3bbb75d97ae4e2e8f7d4834236f86f85f0c85e04ab2e191db1123b04f80b", + "sha256:9e227f3dbe6bde7491cf0a9965d00b88c6b1a4a95d11480ddf88bb96d397c19f", + "sha256:9e25feb9e330b63edb0278a0acdf85e50d0cb0fbf49c3084abbe4e24ae195346", + "sha256:9f098115c247e11d138ab83a28fa0323c77015007ea2df73ba5fd714dfefd67c", + "sha256:a18f38cafc329bac5e3c2b96c765b4c96d3d103421ed22ab7988c1e3fce27464", + "sha256:a4bbd2d87dd233b9fc5812160c3d0ffbe42edc22a26ce0469f58479ede633fe9", + "sha256:a5fcffb37e602b0b3c1638a97746b9b96125caa9bcf6fa41d337a9261de231ee", + "sha256:a8e9f292fcda89b324f2f5c91d13f1424a153e40fc2756f38ee23b15835ff300", + "sha256:a9f54054101545a9a9cccefddf54316aa6e4491611fcbef9e91b3b6bebec04f6", + "sha256:aa2c838cc024642cc04c6854232f32b43e5e22833dd11119c1766c7873b8370d", + "sha256:ac0c7c9f1609b0c4c114feb1d7a3409564c7fb77e360bed9e97e5d25dfeaf868", + "sha256:add96447a86d205ab616665d53b2950ee81083757f56e6ea833c8b2917646b46", + "sha256:ae9dcb8fbe244cb82f8a6458b455b927a03685e383d9bacf1ea5ce180b96dc97", + "sha256:b4a635a0487774f841cb1fb62e907e7195cc95bc761e053184b8acc3ceb20733", + "sha256:b4d12837e0203bbace818ff4a7461afdcd78bcd782351cea148139180d7bcffe", + "sha256:b61687d0828e72bf5cda24a2690188f37170bd31c9359ac97e4e66569f120a16", + "sha256:b807e598953730f82e4eae3bd30f6a122cf6b31c398c6b504c0e04c13c170429", + "sha256:b8cd1f918b26fd7b1832ece557cc18f2d8747309ff8b3f0ef9d4250c5ad67a39", + "sha256:b91cc9d336957239ff200f30097e6fea2dc6d6fb3c81e853eaa09eac904fd894", + "sha256:bd3ce56ae2cbae3ba82b683bc425cd7e48d2ed8b10f3e818186b6f5646d9271c", + "sha256:be6cb0c799abb0e2ba3e618e6d28ddddf7e485f6c2ce938dfa237daf3905072c", + "sha256:befb4158af32106b9a93db8d6d1d1cbbd418c0d5aca0cabb7b1780abf0c89169", + "sha256:bf053da3c97a4bc5ecfbb218cdd2983febd91c617be8367d139882aa11e490aa", + "sha256:c02e8f18bdedba082cef725942ac823b9b60656db07f7e265cb31618dfd00d77", + "sha256:c1bc67752d5f21013cfe430df4062441714eab79f65a6a05e01505957e9c35fe", + "sha256:c61750fadcd119d0825bcb7d7d675dd264dcc89cc05292aab5be68ebdbb374ad", + "sha256:c90d5b3d4e944e065a301d741b3c1d784f6bd1f503aa68b4967e32b2ba313d85", + "sha256:c9a7f43c0b202b334cc9184af09bb8f21d3a209e038efaf106936fb69e6b026e", + "sha256:cb96e6e088d6cf71c1ea977510948320234824cf226e32f6f6e044f7a9c82b34", + "sha256:cf63c214fe879a65e69a386f915e36104fc84254ab141240f8854602d8e0be2a", + "sha256:d1aca03ede943eb80ab3d63bb082c84b7aab85ea83bd0fd0c200260945fb49d9", + "sha256:d2e56fd3b00222722abfb3f5f0759ddbae4b90811b5ad4343c64030ad1bde70c", + "sha256:d5f93ebbeb8032d47e349328ec8662d973d9b05a70b3c35df1f91fe419b84749", + "sha256:d882a373d8093c2941e01291b7ced96e9cbe4781da9a7751ca7e6c70385e5214", + "sha256:d920abdfa61279ba1a2ef9484aab07bf03331f8c08a10120fa332353d06e6932", + "sha256:da2af0d7aebfc2074080d72efa6ab8317c62481ef1f896f65d9999c1c01f4494", + "sha256:dd8ea6ebee7aedbf7c749fa80521d9ccf1ba473e0d1e14805caafbaad281c889", + "sha256:de8b364c423ef0a4bad9069657d617f9a5d2b2062457a89b1fa16ee199c399c1", + "sha256:df1ae86ff54725a01fa1a0510b914ca53a161b7050be74f6204e24aded5971d0", + "sha256:dff05cb7016dff1e9fd68f4122c127b65dfc59de5306cfb7ad92f956f230bee2", + "sha256:e1a622f13970d81f95d0c72f9dc090dce9085fccfa4c9f2174377ee32bd15786", + "sha256:e49fb0d1ce92cfa0cb198cc5b1b11cdf9d0638658e2a2db2687e39db7c87fc78", + "sha256:e5c802729725bd07e2bc3ab7b76dc7e0bbfc53129d8f1eb1c002c24cf774717e", + "sha256:e841068dc0be4cb6dfb5c890eb88cbdcff2f4a332393c7ec94e8e618bd32c1a8", + "sha256:e916035e3e9930cbdfdd10abf48861340221857f45509565898e012263f7b289", + "sha256:eba154571c16e032112afac0dc2dfe9e63c2ceb7aedd07bb7eecf2ce26d4dd4c", + "sha256:f03460ff076f70ab595bb45a0205ccea1971443575b6920c52e755dec2b3fbfe", + "sha256:f0ec3b750b59375eab5b0fb2b9254810c00a3375be6d789899f1055a1d556237", + "sha256:f291bcf42ae98eb5107edb162c3c998b4a89648fd8e99ed4cbd12705292788cd", + "sha256:f61efe1d2fe0de16158a5fe1d1cf3c14bdb6aecd54d8938fd26512c525c1f624", + "sha256:f68edfc67aabac33708941f26f22a7b8e9f81429bc0cf249fcf7d66b23af8d19", + "sha256:fa95848c929b6a75f6848d3c9793e59db365ee436776e57db835cdbfa79ba977", + "sha256:fd9f8797427910198f95bced71ddfed61130d7e349213bfb8466c9c99e2c46a8", + "sha256:fdb4ca07ab75ffadab4a8b135ad59cdbb3156b99310f3d565370da74a15d6bd3" ], "markers": "python_version >= '3.9'", - "version": "==3.0.3" + "version": "==3.0.4" }, "marshmallow": { "hashes": [ @@ -1815,11 +1872,11 @@ }, "mkdocstrings": { "hashes": [ - "sha256:2703708697487d1b6d6d7b412e176fa436edf120c1bf81dc9e126b12d00893c7", - "sha256:a0b8c2bdd29a6416c80d717aa369bbf7831946bd9f23c2a66db1b1dbe7693dbd" + "sha256:0951b147f352118a45a3db85f0f3b660833d34cb5120a2751315b8d08dafd159", + "sha256:9563828667eeb35256f87a6b61fe857676ac628ec1abbce9aea9839a502c9cb1" ], "markers": "python_version >= '3.10'", - "version": "==1.0.6" + "version": "==1.0.7" }, "mkdocstrings-python": { "hashes": [ @@ -1863,190 +1920,192 @@ }, "numpy": { "hashes": [ - "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", - "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", - "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", - "sha256:0a59a421a32580a009e8a1751345bf829631b990dc1794b80514ab722b435def", - "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", - "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", - "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", - "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", - "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", - "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", - "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", - "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", - "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", - "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", - "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", - "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", - "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", - "sha256:4f8929ee6c96bfbd7b4ed2032e0c03af86fe1826740ab61ddabf9072d06e57ff", - "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", - "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", - "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", - "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", - "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", - "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", - "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", - "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", - "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", - "sha256:76c2c1e6bfa5c84adc6434dfbf013aa92096a7985221762c8f11fedfd20fff58", - "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", - "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", - "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", - "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", - "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", - "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", - "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", - "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", - "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", - "sha256:a72f874bc9e10e4b8f80426fb49716d5141f64442a0c8418065093ec8017fbb0", - "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", - "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", - "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", - "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", - "sha256:b5d93cf48f687479941d12b69c873ad2cc76bbd487f0091c2200636497f34034", - "sha256:b7e18c623bb5c95acb3b3328861272816ba199fb531921c5d6d0b675f1fde9e3", - "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", - "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", - "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", - "sha256:bf63afbe037eb5d2fe87fbcc7778e61da53ebaf21d938a4515aa73b62532a5d4", - "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", - "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", - "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", - "sha256:cb189f09db39283b26bfd061ec16189e14f71c6755207f72a0f7540867afe5b9", - "sha256:ccb32e0525d29e8b0572eb84c9a57af0e7a4e615726927506f55063c62414034", - "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", - "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", - "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", - "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", - "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", - "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", - "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", - "sha256:f59a878c33d6b88122d80d239bb3b845d58708750b0cb06a09aebb9b18ec696c", - "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", - "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", - "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", - "sha256:fc36dc566135b5eceec4cf89758fcb719266a019ef07dae1754ae7c9f617ef3e", - "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab" - ], - "markers": "python_version >= '3.12'", - "version": "==2.5.3" + "sha256:067374eb538c34c745436365cf7b0112595c1d326f21ce4ff340f61230239fbb", + "sha256:0b4724a19de67bea8cfc4970798efa78bcbbe2ac2613cfac16721a42d44de2a5", + "sha256:0f02a46e49cfb6c73bdb7aea1c0d3461dbae9aba613542b65f657cd3d17b9fab", + "sha256:1c2e71b04c6cad90026e544501bbe0ab9290fa8a4d845e7e8c0d124fb429c988", + "sha256:1ef3aa6d7e29bb13677323114280b05acc57607fa2300e66432d665d5418a162", + "sha256:2132418bf8dd124a427ca9e6a1daf9ee1a87185344c95119ceae868b99466da1", + "sha256:2199ed071f460487c8db2c0e5c0b564494190edb4772fe80f9aad88b2604def5", + "sha256:2377da2dd3ba2c1200956acbab2a358c83b8e1f8531191672d1cd6ad83250d53", + "sha256:298eca75243f2cbbfdb460560b9fb2a1792a33cf2ab4286efd43d92e8d3df508", + "sha256:2c2c4afffdeb7920e445028dd71eb932cac3e704792e964bc2a232426d4f1255", + "sha256:2ca144f15135b6212a5c47b1e2aeca6e412f102f95a2d5d88d8aec77eb255de3", + "sha256:2fa3328f784fc8277fc48026f6cad516f5c561c5d8e2e39b3c9e0c8f23223b34", + "sha256:325518d4245b9e331387702aa58c2ce1dc4cdcbb41dfb4ccd5dcbc7e08db1266", + "sha256:332f3378fe077dd850e677ec01bdcc4f22368fb5d50ef10b2c79230b1bf5a592", + "sha256:3573cd22564692a5b899ec344e5d5b9cc4576f2985b96f22af3564ed54f2710f", + "sha256:381a7a3d2e65e64c0ec302795ab9dc12bb1e73f150904699c153716177eebdaf", + "sha256:38f47be9f74ab870d2633b5456ae519c43758a8d1fd05342f0ce4ecc034396ee", + "sha256:4054173604cd8658796053f1f3bc0befb68ec1c0762c57fdad61e199256a8617", + "sha256:468397ba3c64427474706e5c9123fe266395496714dc684294eac75cd4930d1e", + "sha256:4e263278bfb5ee6409db8aedbc4cc32973b1b82bc1e8d3c668551d04d83a7e37", + "sha256:5258bc06526964be5face2fc6f756857a3f24f21ec3e72ca131337a75b165d6c", + "sha256:56733449d2544178beaa4545cee357370440cf056c197f9c7bfb19dbfdd0e86d", + "sha256:5ec3753760c1a6d8bb91200666e545c3a9728e6269dfb5d6ce02340996698aa3", + "sha256:5fbf7141bbfd63aea22f435c9062a032b9ea0082fe9845dad7f021d3f1234e71", + "sha256:64d1c8ac28a4077cf987e0a71a7a0ef7e2df70722f07f0baa42dbb7eb6938647", + "sha256:64f9c9878c1938476365e11ccfb6b770f3b9e5f045ccddc514235041e6959365", + "sha256:6c109eac9cd439193678f69d70733c1108487546ca8eafc107b510ae10c1aecd", + "sha256:6d6a71b9d9a97c03633aa12565ef2825ffa036cc1d99cfd50dacf0f128af4fe2", + "sha256:6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0", + "sha256:7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d", + "sha256:77045a4b175bbf5316ec08003880804336c78f92281a1b72222b274ea85ec5ac", + "sha256:7a14a461d9340f1b46b8648578aed9cdb8b3b018a8fac6c1dde2c9192a01a87f", + "sha256:80d6ef6e8620eb2c2b4c4caad50b5935d6db3cde2d51581b55dcc79e14016d1d", + "sha256:81e3420b27048b65eb14c3acf0c174a8cb0e023277716110347d2dcb26026dad", + "sha256:823874a507a84af050493b622affde94b6f7c3a0dc22cb2801381bc03b871c00", + "sha256:8b4d2fd2d34e5f8c9235ee787de5631a37a28402b15cb80814df973d2be54129", + "sha256:8dddfbee2e68d26d0d7d7d9cb247b1fd4409241cce32d815a11d97ec2cfde179", + "sha256:950ea81d57ef070665581b6e1b5f6a029306423cd1739c5b95fe78aa30db6b9d", + "sha256:956555e0603a4d38019ae6925711cb9dc43195c076a928accf7ea5d50bddfe53", + "sha256:98b053943e5a0474ec0da309d2cb9d3f18ea57f8a2067c2ab7b5f763d1068380", + "sha256:9968ab7e49b93ac6e1c3b2239732183152c9150f16308d30b66a372cffe3483c", + "sha256:9a94cf751c9ad8ebaa835bcd3d40dacf8534ad086b88c38029b65123c7999d2a", + "sha256:9cb18a327b49c5c337f972b03682f6a49855525faaf3c0d3e9c96cd0fd8880a8", + "sha256:a7b1b6353e36a7e50de2973a38d705c88ee93adcf120673cee7f45a4a3fa223a", + "sha256:a813ed7719bf45463c51779e6a98d0385fe905e48447526938a4b8337333d551", + "sha256:aa1cce2ff3f8d953de38b76bf44602caeb69f101430208f64a10067f7cb4b1d3", + "sha256:ad62a416ddcf863bf44bba76fbf6b53366ab0692e294f51cae4b5fbe0d246788", + "sha256:aec3fc4b32ff82421274f5d205c559c51c840c8df66a78efd7f3612dd005a26a", + "sha256:b1185012870173de7ae33d370bd45b1cf5baee747ea4b97036b65f4e93016877", + "sha256:b11e8fda06a7d69f15ebf542660b74466c2e51094800c1fb794f47ad4faeef17", + "sha256:b64a85f40e154983960a4167d4c1d57a50c7f109b3d3264a3a984154e90a8454", + "sha256:b86966fbe4ad7de710422175572bcdc75fdedadfb54bc6fab7deabccddd7780b", + "sha256:b89d0aaae2fe498c648f4c4795c084db535af5bd98ef942b2a3681fb74ce8645", + "sha256:bc39ac66a7a9a3fbd6134fda43136b60ffde99c8f4501e64e0d2b24da137babf", + "sha256:c05ede731b03fb1b7591faca9389ade3267d2bddf1ad8882bb3f2cc5e101694f", + "sha256:c6342f54c67093cae5c0227eb0eb772fdb79f2a2c37a6eb278b9909ee06aa356", + "sha256:c668b2f0d651605b58892644b0e302c7157f7159544227758c896982ef384b18", + "sha256:c9b80cdf5cedba0e90d93fa5f9a333c4d65bd545cd669b71bb97ce2b703c9d73", + "sha256:cfd73180400042a7c532d30c5e287bdd03c59ff9ee1b4c0316af0539e29dfe23", + "sha256:d4cccbbc78717966f764cd3af4fb70276fa01fc7a2688af11c78901fa5c04f05", + "sha256:d549420b8858885cea8838a727842249218b9c1da24dd517e25c9c7a948310a3", + "sha256:d8200f16437b289a5bb927c6e184eccc3e8389bc0070fea4cd5b9e13c1757959", + "sha256:e94aef2c639da4a960ad0db8e06471208d8589974953d78b61d345b4eb99e394", + "sha256:fbde6962867ee75b48b0ee29b2b9372ec5d617799dbaf38e82dc0596f2f7738a", + "sha256:fe4d21ab149f15e4e6043dfb0de87e6e5f34ac176cde83060e9802981fca2ac2", + "sha256:ffa6ce09a1c6a08e9667dd9c97aa0b14184e8d18f2a14b78b2a2328c9147f076" + ], + "markers": "python_version < '3.14'", + "version": "==2.5.4" }, "opentelemetry-api": { "hashes": [ - "sha256:711ede81773c8025c2c03dac0450bc89f3d30aea6eabcc815c570d4e35a963f7", - "sha256:80e068aba7cd56c8b58512d6a36f8d25cb1dfaa0c0a4cc1c938ccf9f362d9cb3" + "sha256:aa38ed19bcc084ba42782a73255b3582283eced7ad6dddbd6695189e69adfb75", + "sha256:b31553efa588ae44bc306f863c785c5333a9ecc091248c6ee68b4b6c87fdedfb" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-http-transport": { "extras": [ + "requests", + "requests", "urllib3" ], "hashes": [ - "sha256:2c229b6593eaa22c86d9b8a15843dc23b406dbda00fb138339189aab07923b4e", - "sha256:c82689928a11505a2a0a26a04afe0ce06d270d3db984bc16c1a80ffa902db00e" + "sha256:2f95404bdee7f9d2d529c7de56c7bd86d014d774d8fbf137810e0167f8a492bf", + "sha256:443080203bf52586ce0b2ad901e8951c61833eab1aa539ae6f1f16fe9e8e7952" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-otlp-common": { "hashes": [ - "sha256:35d24c867310f4713a9738b0202b6bade77955418fccb9e2f7eea01b76263916", - "sha256:362268ec6aa705e183776ff938539df1e8ce45bc5509d242538b1d40c26fe6a6" + "sha256:00ff8592c3a7cb729ff3fdc7ffa12372c243bdf2163e80c180994d0c7bd83ee9", + "sha256:6b1403487a2185ac1feb45fd5546fdf8630ce71c36bcefaadf51e2130e9e23f9" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-exporter-otlp-proto-common": { "hashes": [ - "sha256:36495115a0c6a7aa946cfda9d59b6ed4e917b6ab0f75cdaf66bc1b176ec1be1f", - "sha256:7e1410ae6d3ed301f7a74bec96467d519d3f37e52f9d95b309ae125e5a1af863" + "sha256:2e4adcc3a67bcf57804fc49514f0ef64974ca7590aa3491da389852b4a0628f6", + "sha256:2f446183ae7047b036226f1d846c41a834b0e8755ad13b51a51dd38952eb466c" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-exporter-otlp-proto-http": { "hashes": [ - "sha256:2f35496d96809f946f41b8805e6b93aec6c9b71b5fd759b75b6af4c084d992ae", - "sha256:74385f99266dafdefff41f3da4b76f6dba79d237a9d4487e6ff1ee653c74a0a3" + "sha256:24a97cf3753c7fb52fad44a696e452ff371686339e2acf3309e2eda3d0230700", + "sha256:45c218405ce3fd879596924b1874bf9a8f6880206d61065c5a912c8e5c297fb7" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-instrumentation": { "hashes": [ - "sha256:0b32e2fcfec1e4bd48511a3b77ce32bfb1c16dcd5479396b21f9086a4b06c0c9", - "sha256:316715ed7b33a12530c0ae3e8d4cdf26d70e1aad358d7ba256e5dea2d4ceed71" + "sha256:4c4aa14dc9a24a02325a9d4c42c4d0208dbb1374c2b1b8fe6c9392d59f3e1008", + "sha256:e79a510f7d87c72d95e964ddb42193a0d9a75668c027d980eab032ea1322a5ce" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-httpx": { "hashes": [ - "sha256:2735d1868749ca04ce47a3802b0b809e3a00c05a65140e05bac5700e74410539", - "sha256:c89297975f54d67692d3c29e9e33a1bdac47710031cc3ed0d83a0c9765336981" + "sha256:0342a4002c6dbc6c4bf22cc7e698f50f5c8b77f63325c6f40c94ab87e016bf4d", + "sha256:5865a72c68098c85955a271ab8744b480a36e3ee492d35b8cadb93c7c4dbb618" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-requests": { "hashes": [ - "sha256:053dde08fbc1d223058e33d477cf81e2bdcbe7af3893787283339b2b557d4b7a", - "sha256:2f9df08bf99f5da4bcfda70735492b4364bfcc5bd3f48232658e936f97ee1a22" + "sha256:28578f72e68e3a5be3226c618ac9570ed360ef1dd22d5d6e0721c6905a4ccc67", + "sha256:7ba17d984a2bd876b88bf0aafcca27b5e3ff4c1821e0fe07641e380a3cf3a3a2" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-threading": { "hashes": [ - "sha256:4aaf38b96b47a04319b8d96bee4f1daa04e0d6c697525595746aeb61618c002d", - "sha256:4c1c9adf42b83b6f44ca4bb99799e066ccfb1869c2029bc73d862c40a8ef932e" + "sha256:2c217359439d09ca4a4be524db368c05a3cc7f218b85e8086f22664db0a03538", + "sha256:94039c60603060d26524e31aed1181a5d153e43f58462d110dbeb4830687d7d7" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-instrumentation-urllib": { "hashes": [ - "sha256:3eb6d2b9b28bb51188d77092a58eac2ab04852d244acb51333bdf42b0e933c68", - "sha256:a919d8014dcf0065ad42ce5c78dd158c76c7342929f451d726462f7ffa68e367" + "sha256:6f679d785ebcb04b791a889d8a9c0e8edf897a85ab80a7a12b0139c461b14627", + "sha256:f00daa25375fbdf88b7e1a425ef54c704674d8bef639c834de93df9b3f88d7f4" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-proto": { "hashes": [ - "sha256:96ee414f24bc3f61ea8e17dc56b4348d4049d73db3eb17c6b3edf75b5b403300", - "sha256:9731566359d7b8e1ee1e149d8e4ad6865bab965e657ad3387ec2784d16927666" + "sha256:79e0fb95e4616691a469439238aa9224d75779b3e108e895d1aa125ab29ca77c", + "sha256:f38e2a8413053c180cd3d2637fbb279673ec2f6a6e09c995aafa2f452c52b46e" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-sdk": { "hashes": [ - "sha256:20caa5130505e386c67c3da1c76e446c842698ced54c76c6148679539aa97972", - "sha256:5dc634c946546f61b757c5b1781f9fd1a10e96ffaf7357c797e508fe9c57e75e" + "sha256:63d24a6ca645019a631e6a51999c73e93adcac1196ca640b8ae78a7cc4762bf3", + "sha256:c604c11dc429810812348989115fa44bd558772a3d7442afc43d024f2c250ca4" ], "markers": "python_version >= '3.10'", - "version": "==1.45.0" + "version": "==1.45.1" }, "opentelemetry-semantic-conventions": { "hashes": [ - "sha256:175b19dd98c4473f4f43a2b1df59186fd7b4a48cd3f77cbe03438b6d6fda230a", - "sha256:97a77dce484c54861e7eeff7651fd8a806dd3c30e501dc316730215ec36890e6" + "sha256:497ca63bf383723411e8eaf60c8779e9877633c936bb641080adab59d0eb6ec8", + "sha256:d4cddeb4315490b35213f55e2bdc9ac54bb1e4d318927475bed62b35545e581b" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "opentelemetry-util-http": { "hashes": [ - "sha256:7f5e581e94f023b30995f700e41ce319cec82bd542ff1c1c7c9b56d6b10bb687", - "sha256:a2d53859630abe8e5abcc4f867c5a809bb95873033573b1a67f6ef43b57a8627" + "sha256:047dea1a628031f857a5a32261dc0e955bc162d39993ed1cffb8f2cff5ba8a62", + "sha256:8f443d7abcaf29c4a07b373bbd31b5b39132c0ed3c27d015a59dc0323d5b1c58" ], "markers": "python_version >= '3.10'", - "version": "==0.66b0" + "version": "==0.66b1" }, "packaging": { "hashes": [ @@ -2063,73 +2122,69 @@ ], "version": "==0.5.7" }, - "pandarallel": { - "hashes": [ - "sha256:1c2df98ff6441e8ae13ff428ceebaa7ec42d731f7f972c41ce4fdef1d3adf640" - ], - "markers": "python_version >= '3.7'", - "version": "==1.6.5" - }, "pandas": { "hashes": [ - "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", - "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", - "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", - "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", - "sha256:23ebd657a4d38268c7dfbdf089fbc31ea709d82e4923c5ffd4fbd5747133ce73", - "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", - "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", - "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", - "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", - "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", - "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", - "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", - "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", - "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", - "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", - "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", - "sha256:5554c929ccc317d41a5e3d1234f3be588248e61f08a74dd17c9eabb535777dc9", - "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", - "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", - "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", - "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", - "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", - "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", - "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", - "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", - "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", - "sha256:854d00d556406bffe66a4c0802f334c9ad5a96b4f1f868adf036a21b11ef13ff", - "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", - "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", - "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", - "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", - "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", - "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", - "sha256:a637c5cdfa04b6d6e2ecedcb81fc52ffb0fd78ce2ebccc9ea964df9f658de8c8", - "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", - "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", - "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", - "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", - "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", - "sha256:bf1f8a81d04ca90e32a0aceb819d34dbd378a98bf923b6398b9a3ec0bf44de29", - "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", - "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", - "sha256:c503ba5216814e295f40711470446bc3fd00f0faea8a086cbc688808e26f92a2", - "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", - "sha256:d3e28b3e83862ccf4d85ff19cf8c20b2ae7e503881711ff2d534dc8f761131aa", - "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", - "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", - "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", - "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", - "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", - "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", - "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", - "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", - "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", - "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee" + "sha256:0704044b676496b8350e023b09f174a26772456c974a2b11c36bebb558c9490d", + "sha256:085e3786ae6b2e82b406266bce36690f72b9dc1421903ba9296b2981a9fcf586", + "sha256:097090508a1dd335013d39106fc10b20f4fd4a171638e47b77d55798ed9dab6c", + "sha256:1bcb3e9ed29e74a7439cedff9e2aefd3ea65de84d7de9ccb6c194192541bd60e", + "sha256:1e7c0afdcaf6661d795fcefc2f647ddd1136f62cdc153fba177c685d97a87808", + "sha256:1e92d9fa834c7d877130027cddc0cad8dcff97c1f6cca26bd6310f847228b658", + "sha256:22172a92e7ee678ec0140c7af4fc9366b55413834a1cd86af78b3caa0b0574de", + "sha256:253e12cb9081b0afbac607920f6142975966bc315135e09de275fdbaa415d2de", + "sha256:265f562fdd1079f69f3de96dd425c3405224038c0af4f920c54bd240ee2c4640", + "sha256:2a8fc94be2ee5f1d86f97aacd8cc566f81680b6498e76f3007421bb5d98151bf", + "sha256:2e5fa32ff162dfdbc280157d664f44d23049ae414725af9676df339c501d82cd", + "sha256:3ef908d28590b3f42d7070e7ad8f9b34b442b260b7f3c1afb57e0040c58cdb1b", + "sha256:429d9df32731ab01383ed98f2baa7a60368090d1a94fc06019a12062510e8630", + "sha256:47121f9571503f724c9b93e297ab6254ac99c77adf5e9ed085ea419fd585c258", + "sha256:4e25e2e1adee99ddfada6f7206a79ae8e9c8a8861b0e3eaaba165006d3eef18e", + "sha256:4ff44b2cb51cbd691c91f92c4ea6c71e34003f239ebd67c2e857dc898466b49c", + "sha256:50c44cbf5820b6b91a5f74aae04972472aefadd3cd9fbd1010409d85528bd570", + "sha256:569e114072b24fc4970c12e2b4bab252671668a40b324318903380cab0254c0c", + "sha256:583be68728a31d0d750d5b8d9e00f02b153df0d4655f858bde93cb84cfc4227c", + "sha256:5e75072773c1b2f7cb63faa3a6f562aede11f3976f68ed34cb538bc091a28171", + "sha256:5edd0a7abb0986ecce1ac81f56d99b6763f86aa6946dceb6c661224f90af5a19", + "sha256:60d81f9e1799b36f3739e7fff44d1fbb2e8fd5a271b3863e03de9715fccda0fa", + "sha256:62f51d7f651c8054c5e82a69265c98082e795d1442df7ca6edc3a545d61214b1", + "sha256:654aae059295dbba6ecd2328ca12712a2cf1676214c8699f1c29213f7ccf9c34", + "sha256:66b07ef7315a31bfe1089cd3d71a7de781c9dca986762d0b4fe7c0ef17465d10", + "sha256:6ff482fa91fa2bafd92e8fe66ce3645c851824310f295c1f0a2f96e928fc4541", + "sha256:77ccbe5057aece6fc172b9b77f19c04335af6882bc2e10c8f3ee4e6bfb3da553", + "sha256:7dac2d65e9087e8e7b5a45fe15c4920911a221df061ab629943ce016489145c7", + "sha256:83e91d15738d7783c050197cef2f2cf82fc6353dae9865aa87ed1fa16aa4d55a", + "sha256:86fa853a12e0b70927e2b1ee00d56d2224ec9cbb4b9d58348b5ad52d2f21150e", + "sha256:8fe77b408d82e2615674dfed62533b95e18a03610573877422aada4f625d4947", + "sha256:963ca21199097a84c7827c4678b04e30833084fbf8ef44fde3fa7180a29f8fa0", + "sha256:97274c9adf6255bb48c620cd6959805efa7f09ea2167f0e0ae006a448cd2fca7", + "sha256:994a79608263fe1c14cc48ffa7300e2b834b7d1cb406ffe96a08828cb0cdd79b", + "sha256:9ae8073aed8e21d1a7fe263dcdc6840743549722a6738198a0a46000fa9476f2", + "sha256:9dab635a549e58a053c7b0fa054dc0bd7be22f0ed9a720f4a85d5fb993276172", + "sha256:9e492cd4bdba6778de4fe0df7f4590c012161ebcf9902dce01b01dc683105514", + "sha256:a3a22e07fe75347eaacc75b0e85297947af4fba6b4aae23916bd8b6828d0bba3", + "sha256:a4dbd4dc65cbe645b92b8785d0f96dd7311010dc6606cf620e51b07b8788a12a", + "sha256:a77a1a44e4d88f1c6a2a64d3eb12efec8420875722e14279800b173a7c7c2804", + "sha256:b27c8d890e4aa2171437ae2a39de1d215e674158e4865c4023a8b31c932513b2", + "sha256:bd75ed0c840f709fc2ae26ddd9534ac77ca1a48ac0cce521a74acaa85f3340a7", + "sha256:c6e4aae3e9bea26c6c9a20d88d96c86ec4a99b4db5fd516bcb4e829ab2c0ee36", + "sha256:c826e9babb7790142c399f58599d8de679bea059d7b39c5b6efa2096fac37266", + "sha256:cc39303913e2ea129915670de5d1c9fbd647f543bb72e5543bac8baa94e9e42f", + "sha256:d7564d86a94c2eb8ab290b07f63ddaae5c032fa53897c29a2ff2197d43aee8af", + "sha256:d7dcd21238cbb4828ff148481ba01cac8946dc5121457b5aeba28636f8f99a60", + "sha256:db7ec631f26223beee8e5c9e0b8f23c24d8197bbd1d982421d4e3188bea51965", + "sha256:e3dccb584123b399c07562ac4d62543e90ede49ddf8ce3c13ffc64cbe828c281", + "sha256:e7c1905ef02c3d6d43d9dbd5b6ccb4da4870a0b0c821bbc103fbdb6f3ad2707b", + "sha256:eb6900de08ac85f93ac4948aa6b80842eba555875337b8359035ac9c43e92d34", + "sha256:ee913a91669056c1de1a6b733fbfeab711de9e54e3bee2dfa5fe79d9457247d1", + "sha256:ef738d71d1059245b6bb03e312be06d8b3821326a83486c1ad03b9aba3710e44", + "sha256:f3ce8a6968045481e91a3990e797e348ce13db45ee164a7095bbc824e26c09dd", + "sha256:f4e7c52eb108d752e7592268108fd3e98efd76d83a3125cdd06c621c2e44359b", + "sha256:f8029ec0f1f89e4f985929ce1f6626dabf3140d61a4e9c1215afdab34eaf9a5d", + "sha256:fb625f426b375bcc96e3a04c5d5d266cd7be6ae5d6866e0e703382ab5164068c", + "sha256:ff51a4459ed036e93d1eb1bb5e6e7b28685d3cb6b7c12b91c05b31024e234729" ], "markers": "python_version >= '3.9'", - "version": "==2.3.3" + "version": "==3.0.6" }, "paramiko": { "hashes": [ @@ -2149,11 +2204,11 @@ }, "platformdirs": { "hashes": [ - "sha256:29dbf06d96c500bc6bdbce75fb0a14d63279c93b1842f97e72a135b33e856983", - "sha256:eab5f70271a490ef74618bb314fbb86e3c7e82fa3b9c922c2ea0e0a1a155d329" + "sha256:1aa0b0d3f224c1f07c295121e312a5a24a180d6ae5a8425ea1784b3e3863e9c0", + "sha256:3dbcf4cd708f21cf876c4eaa90e58412bc4f033d87143f41b1493ff77c25b7e1" ], - "markers": "python_version >= '3.10'", - "version": "==4.12.2" + "markers": "python_version >= '3.11'", + "version": "==4.13.0" }, "pluggy": { "hashes": [ @@ -2222,11 +2277,11 @@ }, "pycparser": { "hashes": [ - "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", - "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992" + "sha256:51d5a8ba2be0bbe440b99d2112604c95bbbc3c2748a64260186c541e1729cd80", + "sha256:d875f09c3507d00e1aba0eecc6dcadc1352f30fff09dc6bff2f1c2935e97c2bc" ], "markers": "implementation_name != 'PyPy'", - "version": "==3.0" + "version": "==3.11" }, "pyflakes": { "hashes": [ @@ -2246,11 +2301,11 @@ }, "pymdown-extensions": { "hashes": [ - "sha256:4a254b771acfcddc6c110a7f4590d818f1f849e2d5aacf69b4b07c8dd2a9700d", - "sha256:fdb8f47f5d7fd069d10ef2a7d8908e613d7865ff0419d544a0ffd3984e884f11" + "sha256:02da896c362b548d85123e7a377a40024ea6ea938b4e45ca064e408afd994b64", + "sha256:caea66e916b4cfe987b5dabb0a78558e3506988f6353812b0b0288be8f976c16" ], "markers": "python_version >= '3.10'", - "version": "==12.1" + "version": "==12.2" }, "pynacl": { "hashes": [ @@ -2382,11 +2437,11 @@ }, "python-discovery": { "hashes": [ - "sha256:cf87d3627dfb4412437fdd5b13eae402607722998d21567993aedbc59b23c15e", - "sha256:d43fcdef879fe795352bd13ccf8d185ba5a9f86f36cfcd00529f596e737442b3" + "sha256:cd1738ca1d37c86ef9d0b654dd46fcee1e41c97c6add12575e8501ced39afdb4", + "sha256:f0c697f95a3aaec4174a6e5e58f5885e25768684bafc76f1afde384709ebdcf2" ], - "markers": "python_version >= '3.8'", - "version": "==1.6.1" + "markers": "python_version >= '3.9'", + "version": "==1.6.2" }, "pytokens": { "hashes": [ @@ -2436,13 +2491,6 @@ "markers": "python_version >= '3.8'", "version": "==0.4.1" }, - "pytz": { - "hashes": [ - "sha256:464303645bafafd72418898368b2429458f709cf1eb6a15372fbcc396b64da63", - "sha256:9d514388fbc89ca0833203464272ac485b8828568ab73532f5020f17e892a0ff" - ], - "version": "==2026.4" - }, "pyyaml": { "hashes": [ "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", @@ -2556,125 +2604,137 @@ }, "rpds-py": { "hashes": [ - "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", - "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", - "sha256:127e08c0642d880cf32ca47ec2a4a77b901f7e2dd1ad9762adb13955d72ffcc9", - "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", - "sha256:168c733a7112e071bb7a66460e667edfcff06c017a3c523f7a8a8e08d0140804", - "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", - "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", - "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", - "sha256:1e5822dfc2f0d4ab7e745eaa6d85945069329beeccef965af3f3bb26058fcab6", - "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", - "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", - "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", - "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", - "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", - "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", - "sha256:2bfd04c19ddbd6640de0b51894d764bd2758854d5b75bd102d2ef10cb9c293a9", - "sha256:2c54a076ca4d370980ab57bc0e31df57bbe8d41340436a90ef8b1219a3cbb127", - "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", - "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", - "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", - "sha256:30c6dc199b24a5e3e81d50da0f00858c5bbdb2617a750395687f4339c5818171", - "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", - "sha256:3a83ae6c67b7676b9878378547ca8e93ed77a580037bcbcd1d32f739e1e6089c", - "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", - "sha256:421aba32367055614287a4292b6a17f1939c9452299f7a0209c117e990b646d4", - "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", - "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", - "sha256:4cf2d36a2357e4d07bb5a4f98801265327b48256867816cfd2ceb001e9754a8f", - "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", - "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", - "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", - "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", - "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", - "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", - "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", - "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", - "sha256:5e8d07bddee435a2ff6f1920e18feff28d0bc4533e42f4bf6927fbd073312c41", - "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", - "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", - "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", - "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", - "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", - "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", - "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", - "sha256:7b689145a1485c335569bd056464f3243a29af7ed3871c7be31ad624ba239bc7", - "sha256:7f88d653e7b3b779d71ae7454e20dcc9b6bae903f33c269db9f2be41bda3f261", - "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", - "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", - "sha256:83e35b57523816c8613fd0776b40cd8bb9f596b37ddd2692eb4a6bb5ab2f8c93", - "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", - "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", - "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", - "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", - "sha256:8bb68f03f395eb793220b45c097bd4d8c32944393da0fad8b999efac0868fc8c", - "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", - "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", - "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", - "sha256:8d2294a31386bfa251d8c8a39472beee17db67d4f1a6eabea665d35c9a4461c3", - "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", - "sha256:8e65860d238379ed982fd9ba690579b5e95af2f4840f99c772816dbe573cb826", - "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", - "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", - "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", - "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", - "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", - "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", - "sha256:9891e594296ab9dada6551c8e7b387b2721f27a67eecd528412e8906247a7b90", - "sha256:9c1255b302953c86a486b81d330d5ee1d5bd937691ce271b6be0ef0e299eaab7", - "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", - "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", - "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", - "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", - "sha256:a3450b693fde92133e9f51060568a4c31fcca76d5e53bbd611e689ca446517e9", - "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", - "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", - "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", - "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", - "sha256:acc992ab27b15f852c76755eb2ab7dce86585ddadba6fa5946e58556088845b4", - "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", - "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", - "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", - "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", - "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", - "sha256:b5c2dc92304aa48a4a60443b548bb12f12e119d4b72f314015e67b9e1be97fca", - "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", - "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", - "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", - "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", - "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", - "sha256:ca6546b66be9dc4738b1b043d5ebd5488c66c578c5ff0fd0e8065313fe3afb76", - "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", - "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", - "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", - "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", - "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", - "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", - "sha256:db08f45aecde626498fb3df07bcf6d2ec040af42e859a4f5040d79c200342911", - "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", - "sha256:de3eceba0b683bcbb1ab93da016d0270df1f9ae7be716b40214c5dafac6ea45a", - "sha256:dfcc8b909769d19db55c7cc9541eb64b9b774b1057ffffb4f1048070475bb9f9", - "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", - "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", - "sha256:e52655eaf81e32593abedaa4bfe33170c8cfedf3365ed9be6e11e07f148f0278", - "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", - "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", - "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", - "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", - "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", - "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", - "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", - "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", - "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", - "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", - "sha256:f8f23ead891a3b762f35ab3b04623da7056545b48aa60d59957e6789914545da", - "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", - "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef" + "sha256:00ba2d8c7dd4ee537978ddf4b3fbd712bef2d8751603f7f3146b3f4287768e25", + "sha256:01445c8d194aa032a08e944f16567672da1c62dbdbefd8b6d0693032e290cf68", + "sha256:028ad274ea951dac64491b5d1e65712a4aeabfdbdb9fccf797b57bd899b0c495", + "sha256:0483515261947e4e8b8e1375bf7463e7eb6ccfb3d86e7b554d90cd5285f20f32", + "sha256:068c37bba854ec2fe42f7365c640af11dd9895890ccbf2df5070d0c059bd7f96", + "sha256:074a4d198bc34d9a8ea425114fc3ded6d11ec01f6a314a8db67454a5152d8834", + "sha256:07deecbfce94c78473018bc7d10b337cc651d12df87a1eb2cb3e4024bc9c33d0", + "sha256:08dae4a4095150a7c4545a1fb40b98e1ab1744fbc2770d92c977b9dadaa49ab6", + "sha256:0da298fb372dc192610a4b9ecbc68a0cd8b675bbbd1fc519d01b41cfd658333e", + "sha256:0f045bb053c9057720d72c56dffe30dffdc05997b2897a827b9325f0ab6623fa", + "sha256:10e208f2425d973938afcd56e28a7c4be32e27b6a60b5d381f49fb9d8acf9759", + "sha256:136a1c3fe4402b7008bc81cb62ee538481795b61a7e83df88dff3b3f02b726ff", + "sha256:159a7aab5c5e8b112c8830f54717ce56da1252ebdbb526f5be2df2309280b9e7", + "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c", + "sha256:1c2d1f6da5128eabf34e963d7163a818846075a52568250d006c4c953b40f903", + "sha256:1d55198263bb51f557550c6ed2e6d1cb6a6fed6eb5c9120b741c5926bef8a45d", + "sha256:1d77b649e6f7cdf12ca5c2a98dad0ad37f9ea9b6f960408a92f0cb12bb3d04d9", + "sha256:1e8d4d79d828299bf44a55db22a9388ab967b49d17132c88eab0f4360b48da8e", + "sha256:22ffd29a63d71fb1b81552c21f2c2b734949b7ac751a9be70675a939a900839b", + "sha256:2693b2728bbcc48d09a981a356954b0c47c53ff25b545856f28a889ea619f69a", + "sha256:270bdcdaac5d5b6f73c5e22e7e135c7f2a50e789f71d9e241d5be8d90026e19a", + "sha256:2711d29b653b3bce48a63d18b9c6b53274669e6d6c4094dddeb4d9a0e45128b2", + "sha256:2c16ab111bc27c646ba8aa005d0527754edc538ebb636f0b1bf8e244b48d1945", + "sha256:306ee1850d8105b5baf977e78d45fcadd12c1a54678d614c9baf217708446e91", + "sha256:3231c4c0e521dafa5be0c9f114ee2c2ad46650836f2d72caa86801950c3e7044", + "sha256:3890a6aa36e6baa53d5258a2a25d3ef8b37ad165a6ab27a892d7c3e3a432cd69", + "sha256:3a72c11530d71abfb66c8d7696a2f86c43e63fca8b948f1a784ac490f4ec688e", + "sha256:3b5a6f40f0a1486b4b36c888123afc67acdbd9f33235927acf5ff295429a0ba3", + "sha256:3c91c210ae7645626c608400e3519b4a642f837cce09ca830db3beb2e9f274d4", + "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33", + "sha256:3e524c7874ac72884d28e16dd5b8d839fd09e0fe76b020d3fbca23212a7b8c52", + "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9", + "sha256:3edae8c5ddfdb6985d49ae9d150516e5076888879022f91a26c2de9276ce0bdb", + "sha256:3f0e9ac28fc067d4d34b88ae43c48e9489455c97fee9633d851f7eeed5a05d35", + "sha256:42e75466f83cd43f6026c81eab74246efb2bdadafb307b85700632d06c68f299", + "sha256:44b32a7c4f0da3d28af31c259e38ddcff096f855e205ed0671d02fcf44f1ea1c", + "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063", + "sha256:45bc6bccf78b20fd834237d18db64965d7ee68ba7f60440a26c7ab71e7b8d51a", + "sha256:46d80bc76b51a6c24f9944368c28d38b8bcbcea1da4f2f8d3ebc31a67e8c6ec6", + "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12", + "sha256:492e5e428cbe126221611f47e068f01660352feec4ad18bc0f5ea9b2ae88fb14", + "sha256:4b26b03d9d2658ee2fa234f8f4f19f38a09773fe5261028025032e26d4d35af0", + "sha256:4c0d2cb595a420b34d5086db0add011e26e2c09d6a024afbac4228bf8f863a30", + "sha256:4cfaf02209061880210819934de2f4f6aa83dc04dafe6770276acc240a56da31", + "sha256:501909f2e4a1e2dee528ef766fe3c469060ebc17e54a8383d404ba07a81a6f02", + "sha256:50906f5aea24b5a865cbd0a589698288631d9f3a54c3a937c83aefa95a0d14af", + "sha256:54ac2158a6f96cfbabff0b2eedaf94b90c5ec7ca8317fcadc61e1c2b2e0ff6ef", + "sha256:56c6952a9b15047466d0c2347c446a761d4527f89976156341e68f0ce5cc08b0", + "sha256:56cd8b3f77d7b6812f533b662186a1f28316931166ddc00fb893b1b0db7e9888", + "sha256:57492a550a1d88d29d003247e5f78dd8cf04a701fac0e4c8db8745a6d2504e0a", + "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c", + "sha256:5c6ee90dee3e85e055ddfd502d611643d9b0fd94c818220bda84ec3dacd9b27b", + "sha256:5c90e7fa02e8f5de0d10c17595c568ada48c5302e749462c0ea1a4c362111a86", + "sha256:5ce8943f79c2210f7abcc28e86367b03b28d95027fd01c46d2472373ae70c86f", + "sha256:617f59cde379b4f648a09797b7f683d04b90a46344cddab85639da5aff0f5531", + "sha256:6307a0da524939decb8ca4a3933b8ab62525794411d6984fca6726e732804af6", + "sha256:684fd492fff4fead00587544e059be2bbcb6f93454f21fa2a91b66fc7508be82", + "sha256:6b5b393eda5ea42cca1c1a6665f2a4882b4fd5d1777e41ce0545a107fb008c9d", + "sha256:6b723eb406dec5bc9ec516c73ab9c3239a3284e017f7eb89ee2b3258bd504fb7", + "sha256:6b9bf3135b4ad5981df9a73d71a35272d650a2985ae9c2746357b24d59de2448", + "sha256:6beb738155fe8ab8091afdfa5a3226b21c2b1593f1e50ebb90eb25b44dbc0391", + "sha256:6c0dbbcc19735fe5f8b0a54c07659d154a9e69f47e15d0a6ab7299215daf62cb", + "sha256:6cdc537c8633d7fd92a82e2e0d2ab74320a3f63d5e59fb9cf08711e08fe151c4", + "sha256:6eae33003518fd4cb4f83a218d5371469dd3001aa3b87128c005b07762f7fe5e", + "sha256:740d0a99cf9de0b17a3943388e9294a59becf75e7c43421f387bd3c7a9901f7c", + "sha256:75c38c50ab9aca840225d9a9a3810bf11d04bd5c1f186cabbb8aee56db3e9b15", + "sha256:761fdae6728ceb99ab182fad2f0cc1e262f610834dc891aea1d1a2a2e634776f", + "sha256:7664419f27db41d4f1c43a78dccda7dd6e8ef2428df3ee01d0c2a07a6b071297", + "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36", + "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d", + "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b", + "sha256:815d26356930846a40c7bc1366e7b1b0320ab8a063e66c11298a208bed0fd237", + "sha256:8171b44a054e5c67fd748ada04187f1250bf35b95f85e52ab64bcf3331a923bb", + "sha256:821b2755db9194409254012f429c56643416fb96ef9be090be82ec8826b7f477", + "sha256:837c6b305e26fe0f75b15c92cf3b2ba29e0ae19dc40b1c557b026cb426347d0c", + "sha256:839dde845559254f34885267c6878f60d61d5205180226d976fe488d45fa128e", + "sha256:84a6ecc0c940169190d2c23bd969debd48c94dbc855acd60188a68d71d421608", + "sha256:8601470267d938bcb7f3ab1a336100af51a4fd5b6ed030ef52461bb3ef5e7e07", + "sha256:88b5268892fde430d5531f95bc560b6efbbd67c929662c586afd729a96e7461c", + "sha256:8aa5dda18d39b6143eb24809d158f9252c88f402749b6f1b62a506cc7d96cc35", + "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0", + "sha256:96beca19ec79de272e8668585380ff9092c47077c1d7a1e098e00bbd921f4785", + "sha256:9a0460d43603d1fd9ef59c30278531e15d78581721ddb538fa560aa7817ea4ad", + "sha256:a03d57b86d2a51d0a66c92177e2be154ad015f357791d306e714569999cdb4cc", + "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9", + "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025", + "sha256:a3dbc5ed9514908d5046107d7b1346bde71eea61de6e0e4919c19354f97e769f", + "sha256:a431156bb41865fc14cd5d79bb9d7bbed83110b0159e34e62ae30951f96c0009", + "sha256:a575404ebc9cf2e91edd32eaf570ec1430eb900d4f56724ba7dd4bc1fc9c176d", + "sha256:a5cf77eb04f20b720be95265a3e00eb2a14814074255cc27069c551b2db53118", + "sha256:a8763f20692da7df39b0afdd1ba3042b004c50a45994f76c2d9a25641f7673db", + "sha256:ab4b2fda7c2b542f7f9d886cc6a838c5079d2b76f72e6081411faba11adde2c9", + "sha256:addeda51556dac7c1a2f14cda62db8b621cd12afba3091d03a96c72932387eab", + "sha256:b242c27c8f836305a4a72df9cdd564386ac57b807bd252a063223331c9316b37", + "sha256:b4f062343e7ad3fa94f2c66e5ae667dee47ee74dd41a9057c4fbe163236a123d", + "sha256:b5b8b0753718d258fd454283fbd57e14545d3b40583fa672e27cb4f987626bcc", + "sha256:be3e47e2d91aa3942ff9bf4077a505226005abfc39b6f7554a91c1b9393986b9", + "sha256:befc2d6a953e563f8a7bfd87a42c22ebf8a3e980dcb7b6a4d17b70b0e914e8a3", + "sha256:bf35d0568abda97233239ce32896d3ad53fccc537832c104e30c94aa5fb93569", + "sha256:c933c6678c6f116ff8af47a4c6db0868b8ace74af0343016c0ef00f00272ea69", + "sha256:c9d1aca01f49170fdcf5c92761b1fafe97f554b721ca4570c5949fff778f0d4b", + "sha256:cdeaa99ce822dca76cfb1b993e9120c5ea212f2eb66d48950ad63c349668a018", + "sha256:ce4d4f52e2a4324396caddbd45a97d8d7be5f42edd25d2355282a9c34f9b2f7f", + "sha256:d1028417bb44037eb3069c1009bd7b7277212876cda22fbe565b0bca9fab6d2c", + "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f", + "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5", + "sha256:d7fca4eb6df565e2a928f1c7dad92d27db8f9df0f449e76423ed5d7e713ed445", + "sha256:d95a354e02393eada6d7351184671aced9d4cce109dabf927cb7aa99624352a1", + "sha256:d9edf30457d74eebfd76b045535e36f1cd89062566a128a0db2145ca042d787e", + "sha256:dbc2673f9223d420c91145599b3ba45a8a50c207d1976908e5fb5ddb0c9b9429", + "sha256:e01b3c878c8641913e688edd1b3f08658c6783d29cf6b826bd3c0d1ae7a1ffaa", + "sha256:e21c1429e205828ea886a2293a4a2c8e01f4c25d9893ca330e97a6cf73f52e7b", + "sha256:e43d4a1f673e8a1cbd8533e809e02b4bf9d4f2280269bb640436556312121250", + "sha256:e6d198bad4e49dd6732fbd636e2fc5c082f45c8cad0b4acb756b00c82c76072e", + "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83", + "sha256:ea394a937f17a54c51239348bdbe2e3518124c8d4a8951ba04a311d3095bd18f", + "sha256:eac2f5dbafd585dfe31f86a23ebf0d3ba480a9d49ebc87947267b5608d4ea0cd", + "sha256:eb61be926bb81567c1f48bdc8aa22b9855048dc2efd53871f9f7e6e9a5632346", + "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858", + "sha256:ec450527cbf485e13c8d3602a54f428ab0432fdade0ede75efd74b735421c871", + "sha256:eef6a03b0b6d08d0835ccfa8ec8d1bc70525e3801387567137b50c557695e6da", + "sha256:ef0d8c843e2827d6c120ab4687e9423fb1d893db1df27b7c1506615bcb9734a0", + "sha256:ef6b65b03247c54692ad4fd9ee97cb772781927db72e3cb05e70b3db6d1ff14f", + "sha256:f3d6ed6a98cfd19155996605474982cc470d7601746a6439078f1a5a3fa8b050", + "sha256:fce4b85234a0cbad67bf8e6e1201ee815d172c9aebad75f25645bc4d834f8e31", + "sha256:fda1d96e542c37b6c804547dbf489c129fe7c97183a76a5ec275909ba1a063df", + "sha256:fdcd198979b4ecffcc1beba366a7fbcf4eb41243691a82fe52ceb0b902f09c12", + "sha256:fe5ad0664ec772b02c45859041aa17655709cced7a31005817fbbbd988c25567" ], "markers": "python_version >= '3.11'", - "version": "==2026.6.3" + "version": "==2026.9.1" }, "s3transfer": { "hashes": [ @@ -2735,14 +2795,6 @@ ], "version": "==0.9.0" }, - "tzdata": { - "hashes": [ - "sha256:c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81", - "sha256:f1b8bd365d8d210c55353f4d7f8d6d8561c0ba50d704b700d195a9424bba0d79" - ], - "markers": "python_version >= '2'", - "version": "==2026.4" - }, "urllib3": { "hashes": [ "sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3", @@ -2753,11 +2805,11 @@ }, "virtualenv": { "hashes": [ - "sha256:2d1a394ef089220b4606408b184433e49417a6e5590a09c7b8a67fb1e013af56", - "sha256:d7f167214b3c4f69df5386677dae5091dbc9c400fc46080187ae28064c9d453d" + "sha256:3769219a308c5d2f093e7729621ad12a6346b5767a7ec4338414e2a0fb0c526b", + "sha256:5f427d56f39eb7e7447d641dd4b7ab1e9c92793f498f35f26da2e4972c5546ae" ], "markers": "python_version >= '3.9'", - "version": "==21.14.4" + "version": "==21.14.7" }, "watchdog": { "hashes": [ diff --git a/setup.cfg b/setup.cfg index 88203ce89..3e9085626 100644 --- a/setup.cfg +++ b/setup.cfg @@ -73,7 +73,7 @@ tests_require = func-timeout~=4.3 pytest-cov~=4.1.0 pytest-html~=4.1.0 - pandas>=1.5,<3.0 + pandas>=1.5,<4.0 [options.extras_require] dev = @@ -90,7 +90,7 @@ dev = black pre-commit filelock>=3.20.3 - pandas>=1.5,<3.0 + pandas>=1.5,<4.0 tests = pytest~=9.0.3 @@ -103,15 +103,14 @@ tests = func-timeout~=4.3 pytest-cov~=4.1.0 pytest-html~=4.1.0 - pandas>=1.5,<3.0 + pandas>=1.5,<4.0 jsonschema>=4.23.0 pandas = - pandas>=1.5,<3.0 + pandas>=1.5,<4.0 curator = %(pandas)s - pandarallel>=1.6.4 inflection>=0.5.1 networkx>=2.2.8 dataclasses-json>=0.6.1 diff --git a/synapseclient/extensions/curator/CLAUDE.md b/synapseclient/extensions/curator/CLAUDE.md index c7402f2ae..f37a97c5d 100644 --- a/synapseclient/extensions/curator/CLAUDE.md +++ b/synapseclient/extensions/curator/CLAUDE.md @@ -1,4 +1,4 @@ - + ## Project @@ -6,7 +6,7 @@ Schema curation tools for data modeling — JSON Schema generation from CSV/JSON ## Stack -Optional dependencies (gated by `[curator]` extras): pandas, pandarallel, networkx, rdflib, inflection, dataclasses-json. +Optional dependencies (gated by `[curator]` extras): pandas, networkx, rdflib, inflection, dataclasses-json. ## Conventions diff --git a/synapseclient/extensions/curator/schema_generation.py b/synapseclient/extensions/curator/schema_generation.py index 720141aad..b92c28be0 100644 --- a/synapseclient/extensions/curator/schema_generation.py +++ b/synapseclient/extensions/curator/schema_generation.py @@ -43,7 +43,6 @@ def check_curator_imports() -> None: try: import inflection # noqa: F401 import networkx # noqa: F401 - import pandarallel # noqa: F401 import pandas # noqa: F401 import rdflib # noqa: F401 except ImportError as exception: @@ -334,27 +333,18 @@ def find_and_convert_ints( """ - from pandarallel import pandarallel from pandas import DataFrame from pandas.api.types import is_integer - large_manifest_cutoff_size = 1000 # Find integers stored as strings and replace with entries of type np.int64 - if ( - dataframe.size < large_manifest_cutoff_size - ): # If small manifest, iterate as normal for improved performance - ints = dataframe.map( # type: ignore - lambda cell: convert_ints(cell), na_action="ignore" - ).fillna(False) - - else: # parallelize iterations for large manifests - pandarallel.initialize(verbose=1) - ints = dataframe.parallel_applymap( # type: ignore - lambda cell: convert_ints(cell), na_action="ignore" - ).fillna(False) + # Series.map is used per column because DataFrame.map needs pandas 2.1 or + # later, and DataFrame.applymap was removed in pandas 3 + ints = dataframe.apply( + lambda col: col.map(lambda cell: convert_ints(cell), na_action="ignore") + ).fillna(False) # Identify cells converted to integers - is_int = ints.map(is_integer) # type: ignore + is_int = ints.apply(lambda col: col.map(is_integer)) assert isinstance(ints, DataFrame) assert isinstance(is_int, DataFrame) @@ -396,7 +386,7 @@ def convert_floats(dataframe: DATA_FRAME_TYPE) -> DATA_FRAME_TYPE: float_df[col] = to_numeric(float_df[col], errors="coerce").astype("object") # replace values that couldn't be converted to float with the original str values - float_df[col].fillna(dataframe[col][float_df[col].isna()], inplace=True) + float_df[col] = float_df[col].fillna(dataframe[col][float_df[col].isna()]) return float_df @@ -459,7 +449,6 @@ def load_df( """ Universal function to load CSVs and return DataFrames Parses string entries to convert as appropriate to type int, float, and pandas timestamp - Pandarallel is used for type inference for large manifests to improve performance Args: file_path (str): path of csv to open diff --git a/synapseclient/models/mixins/table_components.py b/synapseclient/models/mixins/table_components.py index e23af514c..6735af713 100644 --- a/synapseclient/models/mixins/table_components.py +++ b/synapseclient/models/mixins/table_components.py @@ -230,7 +230,9 @@ def _reformat_special_values(obj): return x for col in df.columns: - df[col] = df[col].apply(_serialize_json_value) + # cast to object first so missing values do not turn an Int64 column into + # Float64 (pandas 3), which would write 1 as 1.0 + df[col] = df[col].astype(object).map(_serialize_json_value) # restore the original values of the column especially for the int64 and float64 columns since apply function changes the dtype df[col] = df[col].convert_dtypes() df[col] = df[col].replace({pd.NA: None}).astype(object) @@ -539,23 +541,30 @@ def _rowset_to_pandas_df( import pandas as pd - def construct_rownames(query_result_bundle, offset=0): + def construct_rownames(rowset, offset=0): try: return ( - row_labels_from_rows( - query_result_bundle.query_result.query_results.rows - ) + row_labels_from_rows(rowset.rows) if row_id_and_version_in_index else None ) except KeyError: # if we don't have row id and version, just number the rows # python3 cast range to list for safety - return list(range(offset, offset + len(rowset["rows"]))) + return list(range(offset, offset + len(rowset.rows))) + + def append_page(column_name, data, index=None, verify_integrity=False): + # add the rows from the next page to the end of the column's series + series[column_name] = pd.concat( + [series[column_name], pd.Series(name=column_name, data=data, index=index)], + verify_integrity=verify_integrity, + ) # first page of rows offset = 0 - rownames = construct_rownames(query_result_bundle, offset) + rownames = construct_rownames( + query_result_bundle.query_result.query_results, offset + ) query_result = query_result_bundle.query_result rowset = query_result.query_results @@ -608,38 +617,25 @@ def construct_rownames(query_result_bundle, offset=0): offset += len(rowset.rows) if not row_id_and_version_in_index: - # TODO: Look into why this isn't being assigned - series["ROW_ID"].append( - pd.Series(name="ROW_ID", data=[row.id for row in rowset.rows]) - ) - series["ROW_VERSION"].append( - pd.Series( - name="ROW_VERSION", - data=[row.version_number for row in rowset.rows], - ) - ) + append_page("ROW_ID", [row.row_id for row in rowset.rows]) + append_page("ROW_VERSION", [row.version_number for row in rowset.rows]) if append_etag: - series["ROW_ETAG"] = pd.Series( - name="ROW_ETAG", - data=[row.etag for row in rowset.rows], - ) + append_page("ROW_ETAG", [row.etag for row in rowset.rows]) for i, header in enumerate(rowset.headers): - column_name = header.name - series[column_name] = pd.concat( - [ - series[column_name], - pd.Series( - name=column_name, - data=[row.values[i] for row in rowset.rows], - index=rownames, - ), - ], + append_page( + header.name, + [row.values[i] for row in rowset.rows], + index=rownames, # can't verify integrity when indices are just numbers instead of 'rowid_rowversion' verify_integrity=row_id_and_version_in_index, ) - return pd.DataFrame(data=series) + df = pd.DataFrame(data=series) + if not row_id_and_version_in_index: + # each page numbers its rows from 0, so renumber the rows across all pages + df = df.reset_index(drop=True) + return df @dataclass diff --git a/synapseclient/models/services/manifest.py b/synapseclient/models/services/manifest.py index e0b9dbcc6..9f5ddd408 100644 --- a/synapseclient/models/services/manifest.py +++ b/synapseclient/models/services/manifest.py @@ -535,6 +535,8 @@ def _apply_synapse_store_defaults(df: "DataFrame") -> None: """ if "synapseStore" not in df.columns: df["synapseStore"] = None + # A blank column is read as float64, which cannot hold bool values + df["synapseStore"] = df["synapseStore"].astype(object) df.loc[df["path"].apply(is_url), "synapseStore"] = False df.loc[df["synapseStore"].isnull(), "synapseStore"] = True df["synapseStore"] = df["synapseStore"].astype(bool) @@ -650,6 +652,10 @@ async def _sort_and_fix_provenance(syn: Synapse, df: DataFrame) -> DataFrame: path, a URL, nor a valid Synapse ID. """ df = df.set_index("path").copy() + # Lists are written into these cells, which a str dtype column cannot hold + for col in ("used", "executed"): + if col in df.columns: + df[col] = df[col].astype(object) results = await asyncio.gather( *[_resolve_row(str(path), row, df, syn) for path, row in df.iterrows()] diff --git a/synapseclient/table.py b/synapseclient/table.py index f9bc30d7f..8b2ca0234 100644 --- a/synapseclient/table.py +++ b/synapseclient/table.py @@ -430,8 +430,11 @@ def _convert_df_date_cols_to_datetime( raise ValueError( "Cannot convert epoch time to integer. Please make sure that the date columns that you specified contain valid epoch time value" ) - df[date_columns] = df[date_columns].apply( - lambda x: pd.to_datetime(x, unit="ms", utc=True) + # pandas 3 returns datetime64[ms, UTC] for unit="ms", so force ns resolution + df[date_columns] = ( + df[date_columns] + .apply(lambda x: pd.to_datetime(x, unit="ms", utc=True)) + .astype("datetime64[ns, UTC]") ) return df @@ -2167,22 +2170,33 @@ def construct_rownames(rowset, offset=0): offset += len(self.rowset["rows"]) if not rowIdAndVersionInIndex: - # TODO: Look into why this isn't being assigned - series["ROW_ID"].append( - pd.Series( - name="ROW_ID", data=[row["id"] for row in self.rowset["rows"]] - ) + series["ROW_ID"] = pd.concat( + [ + series["ROW_ID"], + pd.Series( + name="ROW_ID", + data=[row["rowId"] for row in self.rowset["rows"]], + ), + ] ) - series["ROW_VERSION"].append( - pd.Series( - name="ROW_VERSION", - data=[row["version"] for row in self.rowset["rows"]], - ) + series["ROW_VERSION"] = pd.concat( + [ + series["ROW_VERSION"], + pd.Series( + name="ROW_VERSION", + data=[row["versionNumber"] for row in self.rowset["rows"]], + ), + ] ) if append_etag: - series["ROW_ETAG"] = pd.Series( - name="ROW_ETAG", - data=[row.get("etag") for row in self.rowset["rows"]], + series["ROW_ETAG"] = pd.concat( + [ + series["ROW_ETAG"], + pd.Series( + name="ROW_ETAG", + data=[row.get("etag") for row in self.rowset["rows"]], + ), + ] ) for i, header in enumerate(self.rowset["headers"]): @@ -2200,7 +2214,11 @@ def construct_rownames(rowset, offset=0): verify_integrity=rowIdAndVersionInIndex, ) - return pd.DataFrame(data=series) + df = pd.DataFrame(data=series) + if not rowIdAndVersionInIndex: + # each page numbers its rows from 0, so renumber the rows across all pages + df = df.reset_index(drop=True) + return df def asRowSet(self): # Note that as of stack 60, an empty query will omit the headers field diff --git a/synapseutils/describe_functions.py b/synapseutils/describe_functions.py index fe1b7f57b..93a1ff845 100644 --- a/synapseutils/describe_functions.py +++ b/synapseutils/describe_functions.py @@ -71,7 +71,12 @@ def _describe_wrapper(df: DATA_FRAME_TYPE, syn: Synapse) -> dict[str, dict[str, stats[column]["dtype"] = df[column].dtype.name else: stats[column]["mode"] = df[column].mode()[0] - stats[column]["dtype"] = df[column].dtype.name + # pandas 3 reports "str" for text columns; keep "object" + stats[column]["dtype"] = ( + "object" + if pd.api.types.is_string_dtype(df[column].dtype) + else df[column].dtype.name + ) except TypeError: syn.logger.info(f"Invalid column type: {column}") diff --git a/synapseutils/sync.py b/synapseutils/sync.py index 71cdcc9f1..2ea0d02ee 100644 --- a/synapseutils/sync.py +++ b/synapseutils/sync.py @@ -5,6 +5,7 @@ import csv import datetime import io +import math import os import re import sys @@ -982,6 +983,10 @@ def _write_manifest_data(filename: str, keys: List[str], data: List[dict]) -> No def _sortAndFixProvenance(syn, df): df = df.set_index("path") + # Lists are written into these cells, which a str dtype column cannot hold + for col in PROVENANCE_FIELDS: + if col in df.columns: + df[col] = df[col].astype(object) uploadOrder = {} def _checkProvenace(item, path): @@ -1106,6 +1111,8 @@ async def readManifestFile_async(syn: Synapse, manifestFile: str) -> DATA_FRAME_ df = pd.read_csv(manifestFile, sep="\t") if "synapseStore" not in df: df = df.assign(synapseStore=None) + # A blank column is read as float64, which cannot hold bool values + df["synapseStore"] = df["synapseStore"].astype(object) df.loc[df["path"].apply(is_url), "synapseStore"] = ( False # override synapseStore values to False when path is a url ) @@ -1537,8 +1544,8 @@ async def _manifest_upload( item = _SyncUploadItem( file, - row["used"] if "used" in row else [], - row["executed"] if "executed" in row else [], + _missing_to_empty_list(row["used"]) if "used" in row else [], + _missing_to_empty_list(row["executed"]) if "executed" in row else [], activity_name=row["activityName"] if "activityName" in row else None, activity_description=( row["activityDescription"] if "activityDescription" in row else None @@ -1552,6 +1559,20 @@ async def _manifest_upload( return True +def _missing_to_empty_list( + value: Union[Iterable[str], float, None], +) -> Iterable[str]: + """Return an empty list if value is None or NaN, otherwise return value unchanged. + + With pandas 3, iterrows gives a str dtype row when every cell is a string or + missing, and the missing cells are NaN, not None. An empty list keeps the + used and executed values iterable for the upload. + """ + if value is None or (isinstance(value, float) and math.isnan(value)): + return [] + return value + + def _check_file_name(df, syn: Synapse) -> None: compiled = re.compile(r"^[`\w \-\+\.\(\)]{1,256}$") for _, row in df.iterrows(): diff --git a/tests/integration/synapseclient/deprecated/test_tables.py b/tests/integration/synapseclient/deprecated/test_tables.py index 9e81b9a15..d2bfeb665 100644 --- a/tests/integration/synapseclient/deprecated/test_tables.py +++ b/tests/integration/synapseclient/deprecated/test_tables.py @@ -402,11 +402,17 @@ def test_tables_pandas(syn, project, schedule_for_cleanup): # SYNPY-717 # This is a check for windows + # The client returns DATE columns as datetime64[ns, UTC]. pandas 2 and later can + # give other resolutions, so force ns resolution on the expected column if os.name == "nt": - df["datetime64"] = pd.to_datetime(df["datetime64"], utc=True) + df["datetime64"] = pd.to_datetime(df["datetime64"], utc=True).astype( + "datetime64[ns, UTC]" + ) else: df["datetime64"] = df["datetime64"].astype(int) - df["datetime64"] = pd.to_datetime(df["datetime64"], unit="ms", utc=True) + df["datetime64"] = pd.to_datetime(df["datetime64"], unit="ms", utc=True).astype( + "datetime64[ns, UTC]" + ) # df2 == df gives Dataframe of boolean values; first .all() gives a Series object of ANDed booleans of each column; # second .all() gives a bool that is ANDed value of that Series diff --git a/tests/integration/synapseclient/models/async/test_entityview_async.py b/tests/integration/synapseclient/models/async/test_entityview_async.py index cdb3df376..ea0a23661 100644 --- a/tests/integration/synapseclient/models/async/test_entityview_async.py +++ b/tests/integration/synapseclient/models/async/test_entityview_async.py @@ -416,15 +416,19 @@ def csv_wrapper(*args, **kwargs): check_names=True, check_dtype=False, ) + # dtype=object keeps None for missing values, matching the query output, + # on all pandas versions pd.testing.assert_series_equal( query_results["integer_column"], - pd.Series(test_data["integer_column"], name="integer_column"), + pd.Series( + test_data["integer_column"], name="integer_column", dtype=object + ), check_names=True, check_dtype=False, ) pd.testing.assert_series_equal( query_results["float_column"], - pd.Series(test_data["float_column"], name="float_column"), + pd.Series(test_data["float_column"], name="float_column", dtype=object), check_names=True, check_dtype=False, ) diff --git a/tests/integration/synapseclient/models/async/test_table_async.py b/tests/integration/synapseclient/models/async/test_table_async.py index 600a65410..77d5f0633 100644 --- a/tests/integration/synapseclient/models/async/test_table_async.py +++ b/tests/integration/synapseclient/models/async/test_table_async.py @@ -1010,6 +1010,8 @@ async def test_store_rows_from_df_with_datetime_columns( # The DST column expectations are hardcoded to prove each value converts using the # UTC offset in effect on its own date (12:00-08:00 -> 20:00 UTC and # 12:00-07:00 -> 19:00 UTC), independent of the machine running the test + # dtype=object keeps None for missing values, matching the query output, on all + # pandas versions expected_results = pd.DataFrame( { "column_string": ["value1", "value2", "value3"], @@ -1022,7 +1024,8 @@ async def test_store_rows_from_df_with_datetime_columns( utils.to_unix_epoch_time(date) if date else None for date in naive_dates ], - } + }, + dtype=object, ) pd.testing.assert_frame_equal(results, expected_results, check_dtype=False) @@ -1114,6 +1117,8 @@ async def test_store_rows_from_csv_with_date_columns( include_row_id_and_row_version=False, ) + # dtype=object keeps None for missing values, matching the query output, on all + # pandas versions expected_results = pd.DataFrame( { "column_string": ["value1", "value2", "value3"], @@ -1122,7 +1127,8 @@ async def test_store_rows_from_csv_with_date_columns( None, utils.to_unix_epoch_time(datetime(2024, 2, 20)), ], - } + }, + dtype=object, ) pd.testing.assert_frame_equal(results, expected_results, check_dtype=False) @@ -1170,6 +1176,8 @@ async def test_store_rows_from_csv_with_tz_aware_date_columns( include_row_id_and_row_version=False, ) + # dtype=object keeps None for missing values, matching the query output, on all + # pandas versions expected_results = pd.DataFrame( { "column_string": ["value1", "value2", "value3"], @@ -1178,7 +1186,8 @@ async def test_store_rows_from_csv_with_tz_aware_date_columns( None, 1708459200000, ], # date columns are converted to epoch ms - } + }, + dtype=object, ) pd.testing.assert_frame_equal(results, expected_results, check_dtype=False) diff --git a/tests/unit/synapseclient/deprecated/unit_test_tables.py b/tests/unit/synapseclient/deprecated/unit_test_tables.py index 6f3250eda..36b354c7b 100644 --- a/tests/unit/synapseclient/deprecated/unit_test_tables.py +++ b/tests/unit/synapseclient/deprecated/unit_test_tables.py @@ -1524,6 +1524,96 @@ def test_rowset_asDataFrame__with_ROW_ETAG_column(syn: Synapse) -> None: assert expected_indicies == dataframe.index.values.tolist() +def _multi_page_query_results() -> tuple[dict, list[dict]]: + """Return a first page and 2 next pages of a table query, with 5 rows in total.""" + + def make_rowset(start: int, count: int) -> dict: + return { + "concreteType": "org.sagebionetworks.repo.model.table.RowSet", + "etag": "DEFAULT", + "tableId": "syn11363411", + "headers": [ + {"id": "61770", "columnType": "STRING", "name": "annotationColumn1"}, + ], + "rows": [ + { + "values": [f"value{row_id}"], + "etag": f"etag{row_id}", + "rowId": row_id, + "versionNumber": 1, + } + for row_id in range(start, start + count) + ], + } + + query_result = { + "concreteType": "org.sagebionetworks.repo.model.table.QueryResultBundle", + "maxRowsPerPage": 6990, + "selectColumns": [ + {"id": "61770", "columnType": "STRING", "name": "annotationColumn1"}, + ], + "queryCount": 5, + "queryResult": { + "concreteType": "org.sagebionetworks.repo.model.table.QueryResult", + "nextPageToken": "token1", + "queryResults": make_rowset(0, 2), + }, + } + next_pages = [ + { + "concreteType": "org.sagebionetworks.repo.model.table.QueryResult", + "nextPageToken": "token2", + "queryResults": make_rowset(2, 2), + }, + { + "concreteType": "org.sagebionetworks.repo.model.table.QueryResult", + "queryResults": make_rowset(4, 1), + }, + ] + return query_result, next_pages + + +def test_rowset_asDataFrame__multiple_pages_row_id_and_version_not_in_index( + syn: Synapse, +) -> None: + # GIVEN a query result with 3 pages + query_result, next_pages = _multi_page_query_results() + + with ( + patch.object(syn, "_queryTable", return_value=query_result), + patch.object(syn, "_queryTableNext", side_effect=next_pages), + ): + # WHEN I convert the result with row ID and version as columns + table = syn.tableQuery("select something from syn123", resultsAs="rowset") + dataframe = table.asDataFrame(rowIdAndVersionInIndex=False) + + # THEN the DataFrame has one row for each row in all pages + assert 5 == len(dataframe) + assert [0, 1, 2, 3, 4] == list(dataframe["ROW_ID"]) + assert [1, 1, 1, 1, 1] == list(dataframe["ROW_VERSION"]) + assert [f"etag{i}" for i in range(5)] == list(dataframe["ROW_ETAG"]) + assert [f"value{i}" for i in range(5)] == list(dataframe["annotationColumn1"]) + + +def test_rowset_asDataFrame__multiple_pages_row_id_and_version_not_in_index_has_unique_index( + syn: Synapse, +) -> None: + # GIVEN a query result with 3 pages + query_result, next_pages = _multi_page_query_results() + + with ( + patch.object(syn, "_queryTable", return_value=query_result), + patch.object(syn, "_queryTableNext", side_effect=next_pages), + ): + # WHEN I convert the result with row ID and version as columns + table = syn.tableQuery("select something from syn123", resultsAs="rowset") + dataframe = table.asDataFrame(rowIdAndVersionInIndex=False) + + # THEN the index is unique and numbers the rows across all pages + assert dataframe.index.is_unique + assert [0, 1, 2, 3, 4] == dataframe.index.tolist() + + def test_RowSetTable_len() -> None: schema = Schema( parentId="syn123", id="syn456", columns=[Column(name="column_name", id="123")] diff --git a/tests/unit/synapseclient/extensions/unit_test_curator.py b/tests/unit/synapseclient/extensions/unit_test_curator.py index fbce627f9..8fc4d1e6d 100644 --- a/tests/unit/synapseclient/extensions/unit_test_curator.py +++ b/tests/unit/synapseclient/extensions/unit_test_curator.py @@ -16,6 +16,7 @@ from typing import Any, Callable from unittest.mock import Mock, mock_open, patch +import numpy as np import pandas as pd import pytest @@ -45,6 +46,8 @@ project_id_from_entity_id, ) from synapseclient.extensions.curator.schema_generation import ( + convert_floats, + find_and_convert_ints, generate_jsonld, generate_jsonschema, ) @@ -1967,6 +1970,215 @@ def test_error_names_every_duplicate_once(self): validate_column_order_list(["patientId", "patientId", "patientId"]) +class TestFindAndConvertInts: + """Test cases for find_and_convert_ints in curator.schema_generation.""" + + def test_converts_digit_strings_to_ints(self) -> None: + """Strings of digits become numpy int64 values and are marked as ints.""" + # GIVEN a dataframe where every cell is a string of digits + df = pd.DataFrame({"a": ["1", "22"], "b": ["0", "333"]}) + + # WHEN the integers are found and converted + ints, is_int = find_and_convert_ints(df) + + # THEN every cell is converted to an int64 and marked as an int + assert ints.to_dict("list") == {"a": [1, 22], "b": [0, 333]} + assert all(isinstance(value, np.int64) for value in ints.to_numpy().flatten()) + assert is_int.to_numpy().all() + + @pytest.mark.parametrize( + "value", + ["abc", "1.5", "-1", "1e3", " 1", "", 5, 1.0], + ids=[ + "text", + "float string", + "negative string", + "scientific notation", + "leading space", + "empty string", + "int", + "float", + ], + ) + def test_does_not_convert_other_values(self, value: any) -> None: + """Values that are not strings of digits become False.""" + # GIVEN a dataframe with a value that is not a string of digits + df = pd.DataFrame({"a": [value]}, dtype=object) + + # WHEN the integers are found and converted + ints, is_int = find_and_convert_ints(df) + + # THEN the value is replaced with False and is not marked as an int + # (pandas can give a numpy bool when the whole column is False) + assert isinstance(ints.loc[0, "a"], (bool, np.bool_)) + assert not ints.loc[0, "a"] + assert not is_int.loc[0, "a"] + + def test_missing_values_become_false(self) -> None: + """Missing values become False and are not marked as ints.""" + # GIVEN a dataframe with missing values + df = pd.DataFrame({"a": ["x", None, np.nan]}, dtype=object) + + # WHEN the integers are found and converted + ints, is_int = find_and_convert_ints(df) + + # THEN the missing values are replaced with False + assert ints["a"].tolist() == [False, False, False] + assert is_int["a"].tolist() == [False, False, False] + + def test_mixed_columns(self) -> None: + """Each cell is checked on its own, so a column can be partly converted.""" + # GIVEN a dataframe that mixes digit strings, text, and floats + df = pd.DataFrame( + { + "id": ["1", "2", "3"], + "name": ["x", "10", "y"], + "score": ["1.5", "2", "abc"], + } + ) + + # WHEN the integers are found and converted + ints, is_int = find_and_convert_ints(df) + + # THEN only the digit strings are converted and marked as ints + assert ints.to_dict("list") == { + "id": [1, 2, 3], + "name": [False, 10, False], + "score": [False, 2, False], + } + assert is_int.to_dict("list") == { + "id": [True, True, True], + "name": [False, True, False], + "score": [False, True, False], + } + + def test_returns_dataframes_with_same_shape_and_labels(self) -> None: + """Both results keep the index and columns of the input dataframe.""" + # GIVEN a dataframe with a custom index + df = pd.DataFrame({"a": ["1", "x"], "b": ["y", "2"]}, index=["r1", "r2"]) + + # WHEN the integers are found and converted + ints, is_int = find_and_convert_ints(df) + + # THEN both results are dataframes with the same labels as the input + for result in (ints, is_int): + assert isinstance(result, pd.DataFrame) + assert result.index.tolist() == ["r1", "r2"] + assert result.columns.tolist() == ["a", "b"] + + def test_empty_dataframe(self) -> None: + """An empty dataframe gives empty results.""" + # GIVEN an empty dataframe + df = pd.DataFrame({"a": []}, dtype=object) + + # WHEN the integers are found and converted + ints, is_int = find_and_convert_ints(df) + + # THEN both results are empty + assert ints.empty + assert is_int.empty + + +class TestConvertFloats: + """Test cases for convert_floats in curator.schema_generation.""" + + @pytest.mark.parametrize( + "value, expected", + [ + ("1.5", 1.5), + ("-0.5", -0.5), + ("1e3", 1000.0), + (" 3", 3.0), + ], + ids=["float", "negative", "scientific notation", "leading space"], + ) + def test_converts_numeric_strings_to_floats( + self, value: str, expected: float + ) -> None: + """Strings that represent numbers become floats.""" + # GIVEN a dataframe with a numeric string and a text value + df = pd.DataFrame({"a": [value, "abc"]}, dtype=object) + + # WHEN the floats are converted + float_df = convert_floats(df) + + # THEN the numeric string is converted to a float + assert isinstance(float_df.loc[0, "a"], float) + assert float_df.loc[0, "a"] == expected + + @pytest.mark.parametrize( + "value", + ["abc", "", "nan"], + ids=["text", "empty string", "nan string"], + ) + def test_keeps_non_numeric_strings(self, value: str) -> None: + """Strings that are not numbers keep their original value.""" + # GIVEN a dataframe with a numeric string and a non-numeric string + df = pd.DataFrame({"a": ["1.5", value]}, dtype=object) + + # WHEN the floats are converted + float_df = convert_floats(df) + + # THEN the non-numeric string is not changed + assert float_df.loc[1, "a"] == value + assert isinstance(float_df.loc[1, "a"], str) + + def test_missing_values_stay_missing(self) -> None: + """Missing values are not converted.""" + # GIVEN a dataframe with a missing value + df = pd.DataFrame({"a": ["1.5", None]}, dtype=object) + + # WHEN the floats are converted + float_df = convert_floats(df) + + # THEN the missing value is still missing + # (pandas 2 gives NaN and pandas 3 gives None) + assert float_df.loc[0, "a"] == 1.5 + assert pd.isna(float_df.loc[1, "a"]) + + def test_mixed_columns(self) -> None: + """Each cell is checked on its own, so a column can be partly converted.""" + # GIVEN a dataframe that mixes numeric strings and text + df = pd.DataFrame( + {"a": ["1", "x"], "b": ["y", "2.5"]}, + index=["r1", "r2"], + ) + + # WHEN the floats are converted + float_df = convert_floats(df) + + # THEN only the numeric strings are converted + assert float_df.to_dict("list") == {"a": [1.0, "x"], "b": ["y", 2.5]} + # AND the columns are object dtype, with the same labels as the input + assert all(dtype == object for dtype in float_df.dtypes) + assert float_df.index.tolist() == ["r1", "r2"] + assert float_df.columns.tolist() == ["a", "b"] + + def test_does_not_change_input(self) -> None: + """The input dataframe is not changed.""" + # GIVEN a dataframe with numeric strings + df = pd.DataFrame({"a": ["1.5", "x"]}) + original = df.copy() + + # WHEN the floats are converted + convert_floats(df) + + # THEN the input dataframe is not changed + pd.testing.assert_frame_equal(df, original) + + def test_empty_dataframe(self) -> None: + """An empty dataframe gives an empty result.""" + # GIVEN an empty dataframe + df = pd.DataFrame({"a": []}, dtype=object) + + # WHEN the floats are converted + float_df = convert_floats(df) + + # THEN the result is empty + assert float_df.empty + assert float_df.columns.tolist() == ["a"] + + class TestResolveColumnOrderList: """Test cases for resolve_column_order_list in curator.utils.""" diff --git a/tests/unit/synapseclient/mixins/unit_test_table_components.py b/tests/unit/synapseclient/mixins/unit_test_table_components.py index a14a18bb3..714c42bb2 100644 --- a/tests/unit/synapseclient/mixins/unit_test_table_components.py +++ b/tests/unit/synapseclient/mixins/unit_test_table_components.py @@ -3,7 +3,7 @@ import tempfile from collections import OrderedDict from dataclasses import dataclass, field -from datetime import date, datetime +from datetime import date, datetime, timedelta from io import BytesIO from typing import Any, Dict, List, Optional from unittest.mock import AsyncMock, MagicMock, patch @@ -61,6 +61,7 @@ _query_table_csv, _query_table_next_page, _query_table_row_set, + _rowset_to_pandas_df, _upsert_rows_async, _validate_primary_keys, convert_dtypes_to_json_serializable, @@ -4931,6 +4932,129 @@ async def test_query_table_next_page_basic_functionality( assert result.select_columns[0].id == "12345" +class TestRowsetToPandasDf: + """Test suite for the _rowset_to_pandas_df function with more than one page.""" + + @pytest.fixture(autouse=True, scope="function") + def init_syn(self, syn: Synapse) -> None: + self.syn = syn + + def _make_bundle( + self, start: int, count: int, next_page_token: Optional[QueryNextPageToken] + ) -> QueryResultBundle: + """Make a QueryResultBundle with count rows, starting at row ID start.""" + rows = [ + Row( + row_id=row_id, + version_number=1, + etag=f"etag{row_id}", + values=[f"value{row_id}", str(row_id * 10)], + ) + for row_id in range(start, start + count) + ] + rowset = RowSet( + table_id="syn123", + headers=[ + SelectColumn(name="col1", column_type=ColumnType.STRING), + SelectColumn(name="col2", column_type=ColumnType.INTEGER), + ], + rows=rows, + ) + return QueryResultBundle( + query_result=QueryResult( + query_results=rowset, next_page_token=next_page_token + ) + ) + + def test_multiple_pages_row_id_and_version_not_in_index(self) -> None: + # GIVEN a query result with 3 pages + first_page = self._make_bundle(0, 2, QueryNextPageToken(token="token1")) + next_pages = [ + self._make_bundle(2, 2, QueryNextPageToken(token="token2")), + self._make_bundle(4, 1, None), + ] + + with patch( + "synapseclient.models.mixins.table_components._query_table_next_page", + side_effect=next_pages, + ) as mock_next_page: + # WHEN I convert the result with row ID and version as columns + df = _rowset_to_pandas_df( + query_result_bundle=first_page, + synapse_client=self.syn, + row_id_and_version_in_index=False, + ) + + # THEN all pages are fetched + assert mock_next_page.call_count == 2 + + # AND the DataFrame has one row for each row in all pages + assert len(df) == 5 + assert list(df["ROW_ID"]) == [0, 1, 2, 3, 4] + assert list(df["ROW_VERSION"]) == [1, 1, 1, 1, 1] + assert list(df["ROW_ETAG"]) == [f"etag{i}" for i in range(5)] + assert list(df["col1"]) == [f"value{i}" for i in range(5)] + assert list(df["col2"]) == [str(i * 10) for i in range(5)] + + def test_multiple_pages_row_id_and_version_not_in_index_has_unique_index( + self, + ) -> None: + # GIVEN a query result with 3 pages + first_page = self._make_bundle(0, 2, QueryNextPageToken(token="token1")) + next_pages = [ + self._make_bundle(2, 2, QueryNextPageToken(token="token2")), + self._make_bundle(4, 1, None), + ] + + with patch( + "synapseclient.models.mixins.table_components._query_table_next_page", + side_effect=next_pages, + ): + # WHEN I convert the result with row ID and version as columns + df = _rowset_to_pandas_df( + query_result_bundle=first_page, + synapse_client=self.syn, + row_id_and_version_in_index=False, + ) + + # THEN the index is unique and numbers the rows across all pages + assert df.index.is_unique + assert df.index.tolist() == [0, 1, 2, 3, 4] + + def test_multiple_pages_row_id_and_version_in_index(self) -> None: + # GIVEN a query result with 3 pages + first_page = self._make_bundle(0, 2, QueryNextPageToken(token="token1")) + next_pages = [ + self._make_bundle(2, 2, QueryNextPageToken(token="token2")), + self._make_bundle(4, 1, None), + ] + + with patch( + "synapseclient.models.mixins.table_components._query_table_next_page", + side_effect=next_pages, + ) as mock_next_page: + # WHEN I convert the result with row ID and version in the index + df = _rowset_to_pandas_df( + query_result_bundle=first_page, + synapse_client=self.syn, + row_id_and_version_in_index=True, + ) + + # THEN all pages are fetched + assert mock_next_page.call_count == 2 + + # AND the index has the row ID, version and etag of each row in all pages + assert df.index.tolist() == [f"{i}_1_etag{i}" for i in range(5)] + + # AND the DataFrame has one row for each row in all pages + assert len(df) == 5 + assert "ROW_ID" not in df.columns + assert "ROW_VERSION" not in df.columns + assert "ROW_ETAG" not in df.columns + assert list(df["col1"]) == [f"value{i}" for i in range(5)] + assert list(df["col2"]) == [str(i * 10) for i in range(5)] + + class TestCsvToPandasDf: """Test suite for csv_to_pandas_df function focusing on date and list columns.""" @@ -4972,7 +5096,7 @@ def test_csv_to_pandas_df_with_date_columns(self, csv_with_date_columns): expected_dates = pd.to_datetime( [1609459200000, 1609545600000, 1609632000000], unit="ms", utc=True - ) + ).astype("datetime64[ns, UTC]") # THEN assert the create_date column is equal to the expected dates pd.testing.assert_series_equal( df["created_date"], pd.Series(expected_dates), check_names=False @@ -5049,7 +5173,7 @@ def test_csv_to_pandas_df_with_date_and_list_columns( "name": ["Alice", "Bob", "Charlie"], "created_date": pd.to_datetime( [1609459200000, 1609545600000, 1609632000000], unit="ms", utc=True - ), + ).astype("datetime64[ns, UTC]"), "number": [42, 10, 99], "bool": [True, False, True], "string": ["hello", "world", "test"], @@ -5134,7 +5258,7 @@ def test_csv_to_pandas_df_all_list_types_with_na_in_items_and_date_columns( "string": ["hello", "world", "test"], "created_date": pd.to_datetime( [1609459200000, 1609545600000, 1609632000000], unit="ms", utc=True - ), + ).astype("datetime64[ns, UTC]"), "number_list": [ [1, None, 3], [None, 5], @@ -5207,7 +5331,7 @@ def test_csv_pandas_df_with_row_id_and_version_etag_in_index( "string": ["hello", "world", "test"], "created_date": pd.to_datetime( [1609459200000, 1609545600000, 1609632000000], unit="ms", utc=True - ), + ).astype("datetime64[ns, UTC]"), "number_list": [[1, None, 3], [], [6, None, None, 9]], "bool_list": [[True, None, False], [None, True], [None, None, False]], "string_list": [["tag1", "", "tag3"], ["", "tag2"], ["", "tag4", ""]], @@ -5327,6 +5451,57 @@ def test_convert_na_and_columns_to_object(self): assert list(result["int_col"]) == [1, 2, None, 4] assert list(result["float_col"]) == [1.1, 2.2, None, 4.4] + @pytest.mark.parametrize( + "values, expected_types", + [ + # nullable Int64 with missing values, as made by convert_dtypes() + (pd.array([1, 2, pd.NA, 4], dtype="Int64"), [int, int, type(None), int]), + # a list of ints with None, as given by a user + (pd.Series([1, None, 3]).convert_dtypes(), [int, type(None), int]), + # int64 with no missing values + (pd.Series([1, 2, 3]), [int, int, int]), + # Float64 with missing values stays float + (pd.array([1.5, pd.NA, 3.0], dtype="Float64"), [float, type(None), float]), + ], + ids=["Int64_with_NA", "list_with_None", "int64", "Float64_with_NA"], + ) + def test_number_columns_keep_python_type(self, values, expected_types): + """Test that each value keeps its int or float type. The == check alone + does not catch an int that changed to a float, because 1.0 == 1.""" + # GIVEN a dataframe with a number column + df = pd.DataFrame({"col": values}) + + # WHEN convert_dtypes_to_json_serializable is called + result = convert_dtypes_to_json_serializable(df) + + # THEN each value should have the expected Python type + assert [type(value) for value in result["col"]] == expected_types + + def test_int_column_with_na_is_written_to_csv_as_int(self): + """Test that an int column with missing values is written to CSV without a + decimal part. Synapse rejects "1.0" for an INTEGER column and + "1609502400000.0" for a DATE column.""" + # GIVEN a dataframe with INTEGER and DATE (epoch ms) values and a missing value + df = pd.DataFrame( + { + "int_col": [1, None, 3], + "date_col": [1609502400000, None, 1708459200000], + } + ).convert_dtypes() + + # WHEN convert_dtypes_to_json_serializable is called and the result is + # written to CSV with the same arguments as the upload + result = convert_dtypes_to_json_serializable(df) + csv_text = result.to_csv(index=False, float_format="%.12g") + + # THEN the values should be written as integers + assert csv_text.splitlines() == [ + "int_col,date_col", + "1,1609502400000", + ",", + "3,1708459200000", + ] + def test_row_columns_remain_int(self): """Test that ROW_ID, ROW_VERSION, and ROW_ID.1 columns remain as int while other columns become object""" # GIVEN a dataframe with special columns (ROW_ID, ROW_VERSION, ROW_ID.1) and a regular column @@ -7090,7 +7265,15 @@ def test_uniform_utc_offsets_parsed_as_tz_aware(self): ] } ).convert_dtypes() - pd.testing.assert_frame_equal(result, expected_result) + # pandas 1.5 gives a pytz.FixedOffset timezone and pandas 2+ gives a + # datetime.timezone, so check the offset and compare the values in UTC + assert result["date_col"].dt.tz.utcoffset(None) == timedelta(hours=-8) + pd.testing.assert_frame_equal( + result.assign(date_col=result["date_col"].dt.tz_convert("UTC")), + expected_result.assign( + date_col=expected_result["date_col"].dt.tz_convert("UTC") + ), + ) mock_logger.info.assert_not_called() def test_mixed_utc_offsets_normalized_to_utc(self): diff --git a/tests/unit/synapseclient/models/async/unit_test_manifest_async.py b/tests/unit/synapseclient/models/async/unit_test_manifest_async.py index 63577acfd..e8d848da8 100644 --- a/tests/unit/synapseclient/models/async/unit_test_manifest_async.py +++ b/tests/unit/synapseclient/models/async/unit_test_manifest_async.py @@ -443,6 +443,27 @@ def test_column_is_bool_dtype(self) -> None: _apply_synapse_store_defaults(df) assert df["synapseStore"].dtype == bool + def test_blank_column_read_from_csv(self, tmp_path: Path) -> None: + """A synapseStore column with only blank cells is read as float64. + Local paths default to True and URL paths are set to False.""" + # GIVEN a manifest CSV with a local path and a URL path + manifest = tmp_path / "manifest.csv" + manifest.write_text( + "path,parentId,synapseStore\n/a.txt,syn1,\nhttps://example.com/f.csv,syn1,\n" + ) + + # AND a DataFrame read from the CSV, where the synapseStore column is blank + df = pd.read_csv(manifest) + + # WHEN I apply the synapseStore defaults + _apply_synapse_store_defaults(df) + + # THEN the local path is True and the URL path is False + assert list(df["synapseStore"]) == [True, False] + + # AND the column has the bool dtype + assert df["synapseStore"].dtype == bool + class TestCheckPathAndNormalize: @pytest.fixture(autouse=True) diff --git a/tests/unit/synapseutils/unit_test_synapseutils_sync.py b/tests/unit/synapseutils/unit_test_synapseutils_sync.py index 52f5a5d2f..58c60953d 100644 --- a/tests/unit/synapseutils/unit_test_synapseutils_sync.py +++ b/tests/unit/synapseutils/unit_test_synapseutils_sync.py @@ -210,6 +210,36 @@ def test_read_manifest_file_synapse_store_values_not_set(syn: Synapse) -> None: assert expected_synapseStore == actual_synapseStore +def test_read_manifest_file_synapse_store_values_all_blank(syn: Synapse) -> None: + # GIVEN a manifest with a local path and a URL path, and no synapseStore values + project_id = SYN_123 + header = "path\tparent\tsynapseStore\n" + path1 = os.path.abspath(os.path.expanduser("~/file1.txt")) + path2 = SYNAPSE_URL + row1 = "%s\t%s\t\n" % (path1, project_id) + row2 = "%s\t%s\t\n" % (path2, project_id) + + expected_synapseStore = { + str(path1): True, + str(path2): False, + } + + manifest = StringIO(header + row1 + row2) + with ( + patch.object(syn, "get_async", return_value=Project()), + patch.object(os.path, "isfile", return_value=True), + patch.object(sync, "_check_size_each_file", return_value=Mock()), + ): # side effect mocks values for: file1.txt + # WHEN I read the manifest file + manifest_dataframe = synapseutils.sync.readManifestFile(syn, manifest) + + # THEN the local path is True and the URL path is False + actual_synapseStore = manifest_dataframe.set_index("path")[ + "synapseStore" + ].to_dict() + assert expected_synapseStore == actual_synapseStore + + def test_read_manifest_file_synapse_store_values_are_set(syn: Synapse) -> None: project_id = SYN_123 header = "path\tparent\tsynapseStore\n" @@ -1162,8 +1192,9 @@ def mock_uploader_constructor(*args, **kwargs): expected_path = data["path"][i] expected_parent = data["parent"][i] expected_name = data["name"][i] - expected_used = data["used"][i] - expected_executed = data["executed"][i] + # missing provenance values are given to the upload item as empty lists + expected_used = data["used"][i] or [] + expected_executed = data["executed"][i] or [] expected_annos = expected_anno_data[i] upload_items.append(upload_item_init.return_value) @@ -1180,6 +1211,69 @@ def mock_uploader_constructor(*args, **kwargs): mock_uploader.upload.assert_called_once_with(upload_items) +@patch("os.path.isfile") +async def test_manifest_upload_missing_provenance_builds_dependency_graph( + mock_isfile: MagicMock, syn: Synapse +) -> None: + """Verify that a missing used or executed value in the manifest is treated as + no provenance, and does not break the real dependency graph build.""" + # GIVEN local files that exist + mock_isfile.side_effect = lambda path: path.startswith("/tmp") + + # AND a manifest where one executed value is missing + df = pd.DataFrame( + data={ + "path": ["/tmp/foo", "/tmp/bar"], + "parent": [SYN_123, SYN_123], + "used": [[], ["/tmp/foo"]], + "executed": [math.nan, []], + } + ) + + with patch.object( + _SyncUploader, "_build_tasks_from_dependency_graph", return_value=[] + ) as mock_build_tasks: + # WHEN I upload the manifest + await synapseutils.sync._manifest_upload(syn, df) + + # THEN the dependency graph has no dependencies for the missing value + dependency_graph = mock_build_tasks.call_args.kwargs["dependency_graph"] + assert dependency_graph.path_to_dependencies == { + "/tmp/foo": [], + "/tmp/bar": ["/tmp/foo"], + } + + +@pytest.mark.parametrize( + "value, expected", + [ + (None, []), + (math.nan, []), + (float("nan"), []), + ([], []), + (["/tmp/foo", SYN_123], ["/tmp/foo", SYN_123]), + ("/tmp/foo", "/tmp/foo"), + ("", ""), + ], + ids=[ + "none", + "math_nan", + "float_nan", + "empty_list", + "list", + "str", + "empty_str", + ], +) +def test_missing_to_empty_list(value: Any, expected: Any) -> None: + # GIVEN a used or executed value from a manifest row + # WHEN I convert the value + result = synapseutils.sync._missing_to_empty_list(value) + + # THEN a missing value becomes an empty list, and other values are unchanged + assert result == expected + + class TestSyncUploader: @patch("os.path.isfile") def test_order_items(self, mock_isfile: MagicMock, syn: Synapse) -> None: