From bb2cd40a75dfc7b056f539058e256485675dfa44 Mon Sep 17 00:00:00 2001 From: Adam Thornton Date: Tue, 24 Sep 2024 10:40:36 -0700 Subject: [PATCH] Update deps; add rubin.nublado.client --- .pre-commit-config.yaml | 2 +- pyproject.toml | 1 + requirements/dev.txt | 1280 ++--------------- requirements/main.in | 9 +- requirements/main.txt | 890 +----------- src/mobu/__init__.py | 2 +- src/mobu/exceptions.py | 735 +++++++--- src/mobu/models/business/nublado.py | 129 +- src/mobu/models/user.py | 4 + src/mobu/services/business/base.py | 2 +- src/mobu/services/business/notebookrunner.py | 20 +- src/mobu/services/business/nublado.py | 162 ++- .../services/business/nubladopythonloop.py | 2 +- src/mobu/services/business/tap.py | 2 + src/mobu/services/monkey.py | 27 +- src/mobu/storage/nublado.py | 880 ------------ tests/autostart_test.py | 14 +- tests/business/notebookrunner_test.py | 27 +- tests/business/nubladopythonloop_test.py | 73 +- tests/business/tapquerysetrunner_test.py | 12 +- tests/conftest.py | 101 +- tests/handlers/solitary_test.py | 6 +- tests/monkeyflocker_test.py | 4 +- tests/status_test.py | 2 +- tests/support/jupyter.py | 481 ------- tests/support/monkeyflocker.py | 11 +- 26 files changed, 1135 insertions(+), 3743 deletions(-) delete mode 100644 src/mobu/storage/nublado.py delete mode 100644 tests/support/jupyter.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e6449565..c962612c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: - id: check-toml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.2 + rev: v0.6.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] diff --git a/pyproject.toml b/pyproject.toml index 579dfbb6..fe6af971 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,6 +95,7 @@ warn_required_dynamic_aliases = true warn_untyped_fields = true [tool.pytest.ini_options] +asyncio_default_fixture_loop_scope = "function" asyncio_mode = "strict" filterwarnings = [ # Bug in aiojobs diff --git a/requirements/dev.txt b/requirements/dev.txt index 73ea4bf9..c6bada6f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,284 +1,68 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes --output-file requirements/dev.txt requirements/dev.in -alabaster==1.0.0 \ - --hash=sha256:c00dca57bca26fa62a6d7d0a9fcce65f3e026e9bfe33e9c538fd3fbb2144fd9e \ - --hash=sha256:fc6786402dc3fcb2de3cabd5fe455a2db534b371124f1f21de8731783dec828b +# uv pip compile --output-file requirements/dev.txt requirements/dev.in +alabaster==1.0.0 # via sphinx -annotated-types==0.7.0 \ - --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ - --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 +annotated-types==0.7.0 # via # -c requirements/main.txt # pydantic -anyio==4.4.0 \ - --hash=sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94 \ - --hash=sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7 +anyio==4.6.2.post1 # via # -c requirements/main.txt # httpx -asgi-lifespan==2.1.0 \ - --hash=sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308 \ - --hash=sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f +appnope==0.1.4 + # via ipykernel +asgi-lifespan==2.1.0 # via -r requirements/dev.in -asttokens==2.4.1 \ - --hash=sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24 \ - --hash=sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0 +asttokens==2.4.1 # via stack-data -attrs==24.2.0 \ - --hash=sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346 \ - --hash=sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2 +attrs==24.2.0 # via # jsonschema # jupyter-cache # referencing # scriv -babel==2.16.0 \ - --hash=sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b \ - --hash=sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316 +babel==2.16.0 # via sphinx -beautifulsoup4==4.12.3 \ - --hash=sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051 \ - --hash=sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed +beautifulsoup4==4.12.3 # via pydata-sphinx-theme -certifi==2024.7.4 \ - --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ - --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 +certifi==2024.8.30 # via # -c requirements/main.txt # httpcore # httpx # requests # sphinx-prompt -cfgv==3.4.0 \ - --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ - --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 +cfgv==3.4.0 # via pre-commit -charset-normalizer==3.3.2 \ - --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ - --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ - --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ - --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ - --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ - --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ - --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ - --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ - --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ - --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ - --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ - --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ - --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ - --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ - --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ - --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ - --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ - --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ - --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ - --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ - --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ - --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ - --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ - --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ - --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ - --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ - --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ - --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ - --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ - --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ - --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ - --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ - --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ - --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ - --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ - --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ - --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ - --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ - --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ - --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ - --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ - --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ - --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ - --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ - --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ - --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ - --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ - --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ - --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ - --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ - --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ - --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ - --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ - --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ - --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ - --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ - --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ - --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ - --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ - --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ - --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ - --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ - --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ - --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ - --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ - --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ - --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ - --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ - --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ - --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ - --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ - --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ - --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ - --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ - --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ - --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ - --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ - --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ - --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ - --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ - --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ - --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ - --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ - --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ - --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ - --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ - --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ - --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ - --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ - --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 +charset-normalizer==3.4.0 # via # -c requirements/main.txt # requests -click==8.1.7 \ - --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ - --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de +click==8.1.7 # via # -c requirements/main.txt # click-log # documenteer # jupyter-cache # scriv -click-log==0.4.0 \ - --hash=sha256:3970f8570ac54491237bcdb3d8ab5e3eef6c057df29f8c3d1151a51a9c23b975 \ - --hash=sha256:a43e394b528d52112af599f2fc9e4b7cf3c15f94e53581f74fa6867e68c91756 +click-log==0.4.0 # via scriv -comm==0.2.2 \ - --hash=sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e \ - --hash=sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3 +comm==0.2.2 # via ipykernel -coverage==7.6.1 \ - --hash=sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca \ - --hash=sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d \ - --hash=sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6 \ - --hash=sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989 \ - --hash=sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c \ - --hash=sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b \ - --hash=sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223 \ - --hash=sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f \ - --hash=sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56 \ - --hash=sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3 \ - --hash=sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8 \ - --hash=sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb \ - --hash=sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388 \ - --hash=sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0 \ - --hash=sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a \ - --hash=sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8 \ - --hash=sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f \ - --hash=sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a \ - --hash=sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962 \ - --hash=sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8 \ - --hash=sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391 \ - --hash=sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc \ - --hash=sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2 \ - --hash=sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155 \ - --hash=sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb \ - --hash=sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0 \ - --hash=sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c \ - --hash=sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a \ - --hash=sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004 \ - --hash=sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060 \ - --hash=sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232 \ - --hash=sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93 \ - --hash=sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129 \ - --hash=sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163 \ - --hash=sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de \ - --hash=sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6 \ - --hash=sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23 \ - --hash=sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569 \ - --hash=sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d \ - --hash=sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778 \ - --hash=sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d \ - --hash=sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36 \ - --hash=sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a \ - --hash=sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6 \ - --hash=sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34 \ - --hash=sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704 \ - --hash=sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106 \ - --hash=sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9 \ - --hash=sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862 \ - --hash=sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b \ - --hash=sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255 \ - --hash=sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16 \ - --hash=sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3 \ - --hash=sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133 \ - --hash=sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb \ - --hash=sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657 \ - --hash=sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d \ - --hash=sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca \ - --hash=sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36 \ - --hash=sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c \ - --hash=sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e \ - --hash=sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff \ - --hash=sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7 \ - --hash=sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5 \ - --hash=sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02 \ - --hash=sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c \ - --hash=sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df \ - --hash=sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3 \ - --hash=sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a \ - --hash=sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959 \ - --hash=sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234 \ - --hash=sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc +coverage==7.6.3 # via # -r requirements/dev.in # pytest-cov -debugpy==1.8.5 \ - --hash=sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c \ - --hash=sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226 \ - --hash=sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c \ - --hash=sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3 \ - --hash=sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a \ - --hash=sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a \ - --hash=sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408 \ - --hash=sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44 \ - --hash=sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156 \ - --hash=sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a \ - --hash=sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c \ - --hash=sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7 \ - --hash=sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a \ - --hash=sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf \ - --hash=sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34 \ - --hash=sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0 \ - --hash=sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e \ - --hash=sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb \ - --hash=sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7 \ - --hash=sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b \ - --hash=sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed \ - --hash=sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406 +debugpy==1.8.7 # via ipykernel -decorator==5.1.1 \ - --hash=sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330 \ - --hash=sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186 +decorator==5.1.1 # via ipython -distlib==0.3.8 \ - --hash=sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784 \ - --hash=sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64 +distlib==0.3.9 # via virtualenv -documenteer==1.4.0 \ - --hash=sha256:759fdbf4554449a74df9fb10cfe91984bc1272f0a2c6c688817d1a2525c72881 \ - --hash=sha256:e456e21cb6d0be659b5297de87cb3e60d9bf0fffb63e316dbaba20b38a5f70ee +documenteer==1.4.2 # via -r requirements/dev.in -docutils==0.21.2 \ - --hash=sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f \ - --hash=sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2 +docutils==0.21.2 # via # documenteer # myst-parser @@ -288,150 +72,58 @@ docutils==0.21.2 \ # sphinx-jinja # sphinx-prompt # sphinxcontrib-bibtex -executing==2.0.1 \ - --hash=sha256:35afe2ce3affba8ee97f2d69927fa823b08b472b7b994e36a52a964b93d16147 \ - --hash=sha256:eac49ca94516ccc753f9fb5ce82603156e590b27525a8bc32cce8ae302eb61bc +executing==2.1.0 # via stack-data -fastjsonschema==2.20.0 \ - --hash=sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23 \ - --hash=sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a +fastjsonschema==2.20.0 # via nbformat -filelock==3.15.4 \ - --hash=sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb \ - --hash=sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7 +filelock==3.16.1 # via virtualenv -gitdb==4.0.11 \ - --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ - --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b +gitdb==4.0.11 # via gitpython -gitpython==3.1.43 \ - --hash=sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c \ - --hash=sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff +gitpython==3.1.43 # via documenteer -greenlet==3.0.3 \ - --hash=sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67 \ - --hash=sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6 \ - --hash=sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257 \ - --hash=sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4 \ - --hash=sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676 \ - --hash=sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61 \ - --hash=sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc \ - --hash=sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca \ - --hash=sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7 \ - --hash=sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728 \ - --hash=sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305 \ - --hash=sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6 \ - --hash=sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379 \ - --hash=sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414 \ - --hash=sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04 \ - --hash=sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a \ - --hash=sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf \ - --hash=sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491 \ - --hash=sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559 \ - --hash=sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e \ - --hash=sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274 \ - --hash=sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb \ - --hash=sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b \ - --hash=sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9 \ - --hash=sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b \ - --hash=sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be \ - --hash=sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506 \ - --hash=sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405 \ - --hash=sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113 \ - --hash=sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f \ - --hash=sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5 \ - --hash=sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230 \ - --hash=sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d \ - --hash=sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f \ - --hash=sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a \ - --hash=sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e \ - --hash=sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61 \ - --hash=sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6 \ - --hash=sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d \ - --hash=sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71 \ - --hash=sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22 \ - --hash=sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2 \ - --hash=sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3 \ - --hash=sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067 \ - --hash=sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc \ - --hash=sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881 \ - --hash=sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3 \ - --hash=sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e \ - --hash=sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac \ - --hash=sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53 \ - --hash=sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0 \ - --hash=sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b \ - --hash=sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83 \ - --hash=sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41 \ - --hash=sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c \ - --hash=sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf \ - --hash=sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da \ - --hash=sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33 +greenlet==3.1.1 # via + # -c requirements/main.txt # -r requirements/dev.in - # sqlalchemy -h11==0.14.0 \ - --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ - --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 +h11==0.14.0 # via # -c requirements/main.txt # httpcore -httpcore==1.0.5 \ - --hash=sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61 \ - --hash=sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5 +httpcore==1.0.6 # via # -c requirements/main.txt # httpx -httpx==0.27.2 \ - --hash=sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0 \ - --hash=sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2 +httpx==0.27.2 # via # -c requirements/main.txt # respx -identify==2.6.0 \ - --hash=sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf \ - --hash=sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0 +identify==2.6.1 # via pre-commit -idna==3.8 \ - --hash=sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac \ - --hash=sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603 +idna==3.10 # via # -c requirements/main.txt # anyio # httpx # requests # sphinx-prompt -imagesize==1.4.1 \ - --hash=sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b \ - --hash=sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a +imagesize==1.4.1 # via sphinx -importlib-metadata==8.4.0 \ - --hash=sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1 \ - --hash=sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5 +importlib-metadata==8.5.0 # via # jupyter-cache # myst-nb -iniconfig==2.0.0 \ - --hash=sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 \ - --hash=sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374 +iniconfig==2.0.0 # via pytest -ipykernel==6.29.5 \ - --hash=sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5 \ - --hash=sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215 +ipykernel==6.29.5 # via myst-nb -ipython==8.26.0 \ - --hash=sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c \ - --hash=sha256:e6b347c27bdf9c32ee9d31ae85defc525755a1869f14057e900675b9e8d6e6ff +ipython==8.28.0 # via # ipykernel # myst-nb -jedi==0.19.1 \ - --hash=sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd \ - --hash=sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0 +jedi==0.19.1 # via ipython -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d +jinja2==3.1.4 # via # -c requirements/main.txt # myst-parser @@ -439,195 +131,70 @@ jinja2==3.1.4 \ # sphinx # sphinx-jinja # sphinxcontrib-redoc -jsonschema==4.23.0 \ - --hash=sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4 \ - --hash=sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566 +jsonschema==4.23.0 # via # nbformat # sphinxcontrib-redoc -jsonschema-specifications==2023.12.1 \ - --hash=sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc \ - --hash=sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c +jsonschema-specifications==2024.10.1 # via jsonschema -jupyter-cache==1.0.0 \ - --hash=sha256:594b1c4e29b488b36547e12477645f489dbdc62cc939b2408df5679f79245078 \ - --hash=sha256:d0fa7d7533cd5798198d8889318269a8c1382ed3b22f622c09a9356521f48687 +jupyter-cache==1.0.0 # via myst-nb -jupyter-client==8.6.2 \ - --hash=sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df \ - --hash=sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f +jupyter-client==8.6.3 # via # ipykernel # nbclient -jupyter-core==5.7.2 \ - --hash=sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409 \ - --hash=sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9 +jupyter-core==5.7.2 # via # ipykernel # jupyter-client # nbclient # nbformat -latexcodec==3.0.0 \ - --hash=sha256:6f3477ad5e61a0a99bd31a6a370c34e88733a6bad9c921a3ffcfacada12f41a7 \ - --hash=sha256:917dc5fe242762cc19d963e6548b42d63a118028cdd3361d62397e3b638b6bc5 +latexcodec==3.0.0 # via pybtex -linkify-it-py==2.0.3 \ - --hash=sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048 \ - --hash=sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79 +linkify-it-py==2.0.3 # via markdown-it-py -markdown-it-py==3.0.0 \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb +markdown-it-py==3.0.0 # via # documenteer # mdit-py-plugins # myst-parser # scriv -markupsafe==2.1.5 \ - --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ - --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ - --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ - --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ - --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ - --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ - --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ - --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ - --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ - --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ - --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ - --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ - --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ - --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ - --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ - --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ - --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ - --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ - --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ - --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ - --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ - --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ - --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ - --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ - --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ - --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ - --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ - --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ - --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ - --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ - --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ - --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ - --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ - --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ - --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ - --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ - --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ - --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ - --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ - --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ - --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ - --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ - --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ - --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ - --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ - --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ - --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ - --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ - --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ - --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ - --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ - --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ - --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ - --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ - --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ - --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ - --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ - --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ - --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ - --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 +markupsafe==3.0.1 # via # -c requirements/main.txt # jinja2 -matplotlib-inline==0.1.7 \ - --hash=sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90 \ - --hash=sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca +matplotlib-inline==0.1.7 # via # ipykernel # ipython -mdit-py-plugins==0.4.1 \ - --hash=sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a \ - --hash=sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c +mdit-py-plugins==0.4.2 # via myst-parser -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba +mdurl==0.1.2 # via markdown-it-py -mypy==1.11.2 \ - --hash=sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36 \ - --hash=sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce \ - --hash=sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6 \ - --hash=sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b \ - --hash=sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca \ - --hash=sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24 \ - --hash=sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383 \ - --hash=sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7 \ - --hash=sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86 \ - --hash=sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d \ - --hash=sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4 \ - --hash=sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8 \ - --hash=sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987 \ - --hash=sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385 \ - --hash=sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79 \ - --hash=sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef \ - --hash=sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6 \ - --hash=sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70 \ - --hash=sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca \ - --hash=sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70 \ - --hash=sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12 \ - --hash=sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104 \ - --hash=sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a \ - --hash=sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318 \ - --hash=sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1 \ - --hash=sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b \ - --hash=sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d +mypy==1.12.0 # via -r requirements/dev.in -mypy-extensions==1.0.0 \ - --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ - --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 +mypy-extensions==1.0.0 # via mypy -myst-nb==1.1.1 \ - --hash=sha256:74227c11f76d03494f43b7788659b161b94f4dedef230a2912412bc8c3c9e553 \ - --hash=sha256:8b8f9085287d948eef46cb3764aafc21915e0e981882b8c742719f5b1a84c36f +myst-nb==1.1.2 # via documenteer -myst-parser==4.0.0 \ - --hash=sha256:851c9dfb44e36e56d15d05e72f02b80da21a9e0d07cba96baf5e2d476bb91531 \ - --hash=sha256:b9317997552424448c6096c2558872fdb6f81d3ecb3a40ce84a7518798f3f28d +myst-parser==4.0.0 # via # documenteer # myst-nb -nbclient==0.10.0 \ - --hash=sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09 \ - --hash=sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f +nbclient==0.10.0 # via # jupyter-cache # myst-nb -nbformat==5.10.4 \ - --hash=sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a \ - --hash=sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b +nbformat==5.10.4 # via # jupyter-cache # myst-nb # nbclient -nest-asyncio==1.6.0 \ - --hash=sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe \ - --hash=sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c +nest-asyncio==1.6.0 # via ipykernel -nodeenv==1.9.1 \ - --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ - --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 +nodeenv==1.9.1 # via pre-commit -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +packaging==24.1 # via # -c requirements/main.txt # ipykernel @@ -635,266 +202,68 @@ packaging==24.1 \ # pytest # pytest-sugar # sphinx -parso==0.8.4 \ - --hash=sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18 \ - --hash=sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d +parso==0.8.4 # via jedi -pexpect==4.9.0 \ - --hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \ - --hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f +pexpect==4.9.0 # via ipython -platformdirs==4.2.2 \ - --hash=sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee \ - --hash=sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3 +platformdirs==4.3.6 # via # jupyter-core # virtualenv -pluggy==1.5.0 \ - --hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \ - --hash=sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669 +pluggy==1.5.0 # via pytest -pre-commit==3.8.0 \ - --hash=sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af \ - --hash=sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f +pre-commit==4.0.1 # via -r requirements/dev.in -prompt-toolkit==3.0.47 \ - --hash=sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10 \ - --hash=sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360 +prompt-toolkit==3.0.48 # via ipython -psutil==6.0.0 \ - --hash=sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35 \ - --hash=sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0 \ - --hash=sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c \ - --hash=sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1 \ - --hash=sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3 \ - --hash=sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c \ - --hash=sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd \ - --hash=sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3 \ - --hash=sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0 \ - --hash=sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2 \ - --hash=sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6 \ - --hash=sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d \ - --hash=sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c \ - --hash=sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0 \ - --hash=sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132 \ - --hash=sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14 \ - --hash=sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0 +psutil==6.0.0 # via ipykernel -ptyprocess==0.7.0 \ - --hash=sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35 \ - --hash=sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220 +ptyprocess==0.7.0 # via pexpect -pure-eval==0.2.3 \ - --hash=sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0 \ - --hash=sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42 +pure-eval==0.2.3 # via stack-data -pybtex==0.24.0 \ - --hash=sha256:818eae35b61733e5c007c3fcd2cfb75ed1bc8b4173c1f70b56cc4c0802d34755 \ - --hash=sha256:e1e0c8c69998452fea90e9179aa2a98ab103f3eed894405b7264e517cc2fcc0f +pybtex==0.24.0 # via # pybtex-docutils # sphinxcontrib-bibtex -pybtex-docutils==1.0.3 \ - --hash=sha256:3a7ebdf92b593e00e8c1c538aa9a20bca5d92d84231124715acc964d51d93c6b \ - --hash=sha256:8fd290d2ae48e32fcb54d86b0efb8d573198653c7e2447d5bec5847095f430b9 +pybtex-docutils==1.0.3 # via sphinxcontrib-bibtex -pydantic==2.8.2 \ - --hash=sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a \ - --hash=sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8 +pydantic==2.9.2 # via # -c requirements/main.txt # documenteer -pydantic-core==2.20.1 \ - --hash=sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d \ - --hash=sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f \ - --hash=sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686 \ - --hash=sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482 \ - --hash=sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006 \ - --hash=sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83 \ - --hash=sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6 \ - --hash=sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88 \ - --hash=sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86 \ - --hash=sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a \ - --hash=sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6 \ - --hash=sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a \ - --hash=sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6 \ - --hash=sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6 \ - --hash=sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43 \ - --hash=sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c \ - --hash=sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4 \ - --hash=sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e \ - --hash=sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203 \ - --hash=sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd \ - --hash=sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1 \ - --hash=sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24 \ - --hash=sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc \ - --hash=sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc \ - --hash=sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3 \ - --hash=sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598 \ - --hash=sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98 \ - --hash=sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331 \ - --hash=sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2 \ - --hash=sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a \ - --hash=sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6 \ - --hash=sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688 \ - --hash=sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91 \ - --hash=sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa \ - --hash=sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b \ - --hash=sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0 \ - --hash=sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840 \ - --hash=sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c \ - --hash=sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd \ - --hash=sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3 \ - --hash=sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231 \ - --hash=sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1 \ - --hash=sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953 \ - --hash=sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250 \ - --hash=sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a \ - --hash=sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2 \ - --hash=sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20 \ - --hash=sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434 \ - --hash=sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab \ - --hash=sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703 \ - --hash=sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a \ - --hash=sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2 \ - --hash=sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac \ - --hash=sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611 \ - --hash=sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121 \ - --hash=sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e \ - --hash=sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b \ - --hash=sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09 \ - --hash=sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906 \ - --hash=sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9 \ - --hash=sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7 \ - --hash=sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b \ - --hash=sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987 \ - --hash=sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c \ - --hash=sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b \ - --hash=sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e \ - --hash=sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237 \ - --hash=sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1 \ - --hash=sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19 \ - --hash=sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b \ - --hash=sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad \ - --hash=sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0 \ - --hash=sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94 \ - --hash=sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312 \ - --hash=sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f \ - --hash=sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669 \ - --hash=sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1 \ - --hash=sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe \ - --hash=sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99 \ - --hash=sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a \ - --hash=sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a \ - --hash=sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52 \ - --hash=sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c \ - --hash=sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad \ - --hash=sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1 \ - --hash=sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a \ - --hash=sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f \ - --hash=sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a \ - --hash=sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27 +pydantic-core==2.23.4 # via # -c requirements/main.txt # pydantic -pydata-sphinx-theme==0.12.0 \ - --hash=sha256:7a07c3ac1fb1cfbb5f7d1e147a9500fb120e329d610e0fa2caac4a645141bdd9 \ - --hash=sha256:c17dbab67a3774f06f34f6378e896fcd0668cc8b5da1c1ba017e65cf1df0af58 +pydata-sphinx-theme==0.12.0 # via documenteer -pygments==2.18.0 \ - --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ - --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a +pygments==2.18.0 # via # ipython # pydata-sphinx-theme # sphinx # sphinx-prompt -pylatexenc==2.10 \ - --hash=sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3 +pylatexenc==2.10 # via documenteer -pytest==8.3.2 \ - --hash=sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5 \ - --hash=sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce +pytest==8.3.3 # via # -r requirements/dev.in # pytest-asyncio # pytest-cov # pytest-mock # pytest-sugar -pytest-asyncio==0.24.0 \ - --hash=sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b \ - --hash=sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276 +pytest-asyncio==0.24.0 # via -r requirements/dev.in -pytest-cov==5.0.0 \ - --hash=sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652 \ - --hash=sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857 +pytest-cov==5.0.0 # via -r requirements/dev.in -pytest-mock==3.14.0 \ - --hash=sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f \ - --hash=sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0 +pytest-mock==3.14.0 # via -r requirements/dev.in -pytest-sugar==1.0.0 \ - --hash=sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a \ - --hash=sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd +pytest-sugar==1.0.0 # via -r requirements/dev.in -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 +python-dateutil==2.9.0.post0 # via jupyter-client -pyyaml==6.0.2 \ - --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ - --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ - --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ - --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ - --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ - --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ - --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ - --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ - --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ - --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ - --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ - --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ - --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ - --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ - --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ - --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ - --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ - --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ - --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ - --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ - --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ - --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ - --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ - --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ - --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ - --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ - --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ - --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ - --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ - --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ - --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ - --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ - --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ - --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ - --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ - --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ - --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ - --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ - --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ - --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ - --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ - --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ - --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ - --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ - --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ - --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ - --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ - --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ - --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ - --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ - --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ - --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ - --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 +pyyaml==6.0.2 # via # -c requirements/main.txt # documenteer @@ -903,305 +272,56 @@ pyyaml==6.0.2 \ # myst-parser # pre-commit # pybtex + # sphinxcontrib-mermaid # sphinxcontrib-redoc -pyzmq==26.2.0 \ - --hash=sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6 \ - --hash=sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a \ - --hash=sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9 \ - --hash=sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f \ - --hash=sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37 \ - --hash=sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc \ - --hash=sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed \ - --hash=sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097 \ - --hash=sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d \ - --hash=sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52 \ - --hash=sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6 \ - --hash=sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6 \ - --hash=sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2 \ - --hash=sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282 \ - --hash=sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3 \ - --hash=sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732 \ - --hash=sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5 \ - --hash=sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18 \ - --hash=sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306 \ - --hash=sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f \ - --hash=sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3 \ - --hash=sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b \ - --hash=sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277 \ - --hash=sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a \ - --hash=sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797 \ - --hash=sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca \ - --hash=sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c \ - --hash=sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f \ - --hash=sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5 \ - --hash=sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a \ - --hash=sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44 \ - --hash=sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20 \ - --hash=sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4 \ - --hash=sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8 \ - --hash=sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780 \ - --hash=sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386 \ - --hash=sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5 \ - --hash=sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2 \ - --hash=sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0 \ - --hash=sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971 \ - --hash=sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b \ - --hash=sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50 \ - --hash=sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c \ - --hash=sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f \ - --hash=sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231 \ - --hash=sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c \ - --hash=sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08 \ - --hash=sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5 \ - --hash=sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6 \ - --hash=sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073 \ - --hash=sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e \ - --hash=sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4 \ - --hash=sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317 \ - --hash=sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3 \ - --hash=sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072 \ - --hash=sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad \ - --hash=sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a \ - --hash=sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb \ - --hash=sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd \ - --hash=sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f \ - --hash=sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef \ - --hash=sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5 \ - --hash=sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187 \ - --hash=sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711 \ - --hash=sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988 \ - --hash=sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640 \ - --hash=sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c \ - --hash=sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764 \ - --hash=sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1 \ - --hash=sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1 \ - --hash=sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289 \ - --hash=sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb \ - --hash=sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a \ - --hash=sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218 \ - --hash=sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c \ - --hash=sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf \ - --hash=sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7 \ - --hash=sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8 \ - --hash=sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726 \ - --hash=sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9 \ - --hash=sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93 \ - --hash=sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88 \ - --hash=sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115 \ - --hash=sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6 \ - --hash=sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672 \ - --hash=sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2 \ - --hash=sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea \ - --hash=sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc \ - --hash=sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b \ - --hash=sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa \ - --hash=sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003 \ - --hash=sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797 \ - --hash=sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940 \ - --hash=sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db \ - --hash=sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc \ - --hash=sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27 \ - --hash=sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3 \ - --hash=sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e \ - --hash=sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98 \ - --hash=sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b \ - --hash=sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629 \ - --hash=sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9 \ - --hash=sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6 \ - --hash=sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec \ - --hash=sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951 \ - --hash=sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae \ - --hash=sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4 \ - --hash=sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6 \ - --hash=sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919 +pyzmq==26.2.0 # via # ipykernel # jupyter-client -referencing==0.35.1 \ - --hash=sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c \ - --hash=sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de +referencing==0.35.1 # via # jsonschema # jsonschema-specifications -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 +requests==2.32.3 # via # -c requirements/main.txt # documenteer # scriv # sphinx # sphinxcontrib-youtube -respx==0.21.1 \ - --hash=sha256:05f45de23f0c785862a2c92a3e173916e8ca88e4caad715dd5f68584d6053c20 \ - --hash=sha256:0bd7fe21bfaa52106caa1223ce61224cf30786985f17c63c5d71eff0307ee8af +respx==0.21.1 # via -r requirements/dev.in -rpds-py==0.20.0 \ - --hash=sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c \ - --hash=sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585 \ - --hash=sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5 \ - --hash=sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6 \ - --hash=sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef \ - --hash=sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2 \ - --hash=sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29 \ - --hash=sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318 \ - --hash=sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b \ - --hash=sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399 \ - --hash=sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739 \ - --hash=sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee \ - --hash=sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174 \ - --hash=sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a \ - --hash=sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344 \ - --hash=sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2 \ - --hash=sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03 \ - --hash=sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5 \ - --hash=sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22 \ - --hash=sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e \ - --hash=sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96 \ - --hash=sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91 \ - --hash=sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752 \ - --hash=sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075 \ - --hash=sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253 \ - --hash=sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee \ - --hash=sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad \ - --hash=sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5 \ - --hash=sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce \ - --hash=sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7 \ - --hash=sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b \ - --hash=sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8 \ - --hash=sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57 \ - --hash=sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3 \ - --hash=sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec \ - --hash=sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209 \ - --hash=sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921 \ - --hash=sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045 \ - --hash=sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074 \ - --hash=sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580 \ - --hash=sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7 \ - --hash=sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5 \ - --hash=sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3 \ - --hash=sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0 \ - --hash=sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24 \ - --hash=sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139 \ - --hash=sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db \ - --hash=sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc \ - --hash=sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789 \ - --hash=sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f \ - --hash=sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2 \ - --hash=sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c \ - --hash=sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232 \ - --hash=sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6 \ - --hash=sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c \ - --hash=sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29 \ - --hash=sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489 \ - --hash=sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94 \ - --hash=sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751 \ - --hash=sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2 \ - --hash=sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda \ - --hash=sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9 \ - --hash=sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51 \ - --hash=sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c \ - --hash=sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8 \ - --hash=sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989 \ - --hash=sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511 \ - --hash=sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1 \ - --hash=sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2 \ - --hash=sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150 \ - --hash=sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c \ - --hash=sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965 \ - --hash=sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f \ - --hash=sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58 \ - --hash=sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b \ - --hash=sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f \ - --hash=sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d \ - --hash=sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821 \ - --hash=sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de \ - --hash=sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121 \ - --hash=sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855 \ - --hash=sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272 \ - --hash=sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60 \ - --hash=sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02 \ - --hash=sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1 \ - --hash=sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140 \ - --hash=sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879 \ - --hash=sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940 \ - --hash=sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364 \ - --hash=sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4 \ - --hash=sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e \ - --hash=sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420 \ - --hash=sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5 \ - --hash=sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24 \ - --hash=sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c \ - --hash=sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf \ - --hash=sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f \ - --hash=sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e \ - --hash=sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab \ - --hash=sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08 \ - --hash=sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92 \ - --hash=sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a \ - --hash=sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8 +rpds-py==0.20.0 # via # jsonschema # referencing -ruff==0.6.2 \ - --hash=sha256:094600ee88cda325988d3f54e3588c46de5c18dae09d683ace278b11f9d4d534 \ - --hash=sha256:1175d39faadd9a50718f478d23bfc1d4da5743f1ab56af81a2b6caf0a2394f23 \ - --hash=sha256:17002fe241e76544448a8e1e6118abecbe8cd10cf68fde635dad480dba594570 \ - --hash=sha256:239ee6beb9e91feb8e0ec384204a763f36cb53fb895a1a364618c6abb076b3be \ - --hash=sha256:279d5f7d86696df5f9549b56b9b6a7f6c72961b619022b5b7999b15db392a4da \ - --hash=sha256:2aed7e243be68487aa8982e91c6e260982d00da3f38955873aecd5a9204b1d66 \ - --hash=sha256:316d418fe258c036ba05fbf7dfc1f7d3d4096db63431546163b472285668132b \ - --hash=sha256:3dbeac76ed13456f8158b8f4fe087bf87882e645c8e8b606dd17b0b66c2c1158 \ - --hash=sha256:5b939f9c86d51635fe486585389f54582f0d65b8238e08c327c1534844b3bb9a \ - --hash=sha256:5c8cbc6252deb3ea840ad6a20b0f8583caab0c5ef4f9cca21adc5a92b8f79f3c \ - --hash=sha256:7438a7288f9d67ed3c8ce4d059e67f7ed65e9fe3aa2ab6f5b4b3610e57e3cb56 \ - --hash=sha256:7db6880c53c56addb8638fe444818183385ec85eeada1d48fc5abe045301b2f1 \ - --hash=sha256:a8f310d63af08f583363dfb844ba8f9417b558199c58a5999215082036d795a1 \ - --hash=sha256:d0d62ca91219f906caf9b187dea50d17353f15ec9bb15aae4a606cd697b49b4c \ - --hash=sha256:d371f7fc9cec83497fe7cf5eaf5b76e22a8efce463de5f775a1826197feb9df8 \ - --hash=sha256:d72b8b3abf8a2d51b7b9944a41307d2f442558ccb3859bbd87e6ae9be1694a5d \ - --hash=sha256:d9f3469c7dd43cd22eb1c3fc16926fb8258d50cb1b216658a07be95dd117b0f2 \ - --hash=sha256:f28fcd2cd0e02bdf739297516d5643a945cc7caf09bd9bcb4d932540a5ea4fa9 +ruff==0.7.0 # via -r requirements/dev.in -scriv==1.5.1 \ - --hash=sha256:30ae9ff8d144f8e0cf394c4e1d379542f1b3823767642955b54ec40dc00b32b6 \ - --hash=sha256:a3adc657733b4124fcb54527a5f3daab0d3c300de82d0fd2b9b297b243151b78 +scriv==1.5.1 # via -r requirements/dev.in -setuptools==74.0.0 \ - --hash=sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f \ - --hash=sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e - # via documenteer -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 +setuptools==75.2.0 + # via + # documenteer + # sphinxcontrib-bibtex +six==1.16.0 # via # asttokens # pybtex # python-dateutil # sphinxcontrib-redoc -smmap==5.0.1 \ - --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ - --hash=sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da +smmap==5.0.1 # via gitdb -sniffio==1.3.1 \ - --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ - --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc +sniffio==1.3.1 # via # -c requirements/main.txt # anyio # asgi-lifespan # httpx -snowballstemmer==2.2.0 \ - --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \ - --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a +snowballstemmer==2.2.0 # via sphinx -soupsieve==2.6 \ - --hash=sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb \ - --hash=sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9 +soupsieve==2.6 # via beautifulsoup4 -sphinx==8.0.2 \ - --hash=sha256:0cce1ddcc4fd3532cf1dd283bc7d886758362c5c1de6598696579ce96d8ffa5b \ - --hash=sha256:56173572ae6c1b9a38911786e206a110c9749116745873feae4f9ce88e59391d +sphinx==8.1.3 # via # documenteer # myst-nb @@ -1215,170 +335,64 @@ sphinx==8.0.2 \ # sphinx-prompt # sphinxcontrib-bibtex # sphinxcontrib-jquery + # sphinxcontrib-mermaid # sphinxcontrib-redoc # sphinxcontrib-youtube # sphinxext-opengraph # sphinxext-rediraffe -sphinx-autodoc-typehints==2.2.3 \ - --hash=sha256:b7058e8c5831e5598afca1a78fda0695d3291388d954464a6e480c36198680c0 \ - --hash=sha256:fde3d888949bd0a91207cf1e54afda58121dbb4bf1f183d0cc78a0826654c974 +sphinx-autodoc-typehints==2.5.0 # via documenteer -sphinx-automodapi==0.17.0 \ - --hash=sha256:4d029cb79eef29413e94ab01bb0177ebd2d5ba86e9789b73575afe9c06ae1501 \ - --hash=sha256:7ccdadad57add4aa9149d9f2bb5cf28c8f8b590280b4735b1156ea8355c423a1 +sphinx-automodapi==0.18.0 # via documenteer -sphinx-copybutton==0.5.2 \ - --hash=sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd \ - --hash=sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e +sphinx-copybutton==0.5.2 # via documenteer -sphinx-design==0.6.1 \ - --hash=sha256:b11f37db1a802a183d61b159d9a202314d4d2fe29c163437001324fe2f19549c \ - --hash=sha256:b44eea3719386d04d765c1a8257caca2b3e6f8421d7b3a5e742c0fd45f84e632 +sphinx-design==0.6.1 # via documenteer -sphinx-jinja==2.0.2 \ - --hash=sha256:705ebeb9b7a6018ca3f93724315a7c1effa6ba3db44d630e7eaaa15e4ac081a8 \ - --hash=sha256:c6232b59a894139770be1dc6d0b00a379e4288ce78157904e1f8473dea3e0718 +sphinx-jinja==2.0.2 # via documenteer -sphinx-prompt==1.9.0 \ - --hash=sha256:471b3c6d466dce780a9b167d9541865fd4e9a80ed46e31b06a52a0529ae995a1 \ - --hash=sha256:fd731446c03f043d1ff6df9f22414495b23067c67011cc21658ea8d36b3575fc +sphinx-prompt==1.9.0 # via documenteer -sphinxcontrib-applehelp==2.0.0 \ - --hash=sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1 \ - --hash=sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5 +sphinxcontrib-applehelp==2.0.0 # via sphinx -sphinxcontrib-bibtex==2.6.2 \ - --hash=sha256:10d45ebbb19207c5665396c9446f8012a79b8a538cb729f895b5910ab2d0b2da \ - --hash=sha256:f487af694336f28bfb7d6a17070953a7d264bec43000a2379724274f5f8d70ae +sphinxcontrib-bibtex==2.6.3 # via documenteer -sphinxcontrib-devhelp==2.0.0 \ - --hash=sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad \ - --hash=sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2 +sphinxcontrib-devhelp==2.0.0 # via sphinx -sphinxcontrib-htmlhelp==2.1.0 \ - --hash=sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8 \ - --hash=sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9 +sphinxcontrib-htmlhelp==2.1.0 # via sphinx -sphinxcontrib-jquery==4.1 \ - --hash=sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a \ - --hash=sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae +sphinxcontrib-jquery==4.1 # via documenteer -sphinxcontrib-jsmath==1.0.1 \ - --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \ - --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8 +sphinxcontrib-jsmath==1.0.1 # via sphinx -sphinxcontrib-mermaid==0.9.2 \ - --hash=sha256:252ef13dd23164b28f16d8b0205cf184b9d8e2b714a302274d9f59eb708e77af \ - --hash=sha256:6795a72037ca55e65663d2a2c1a043d636dc3d30d418e56dd6087d1459d98a5d +sphinxcontrib-mermaid==1.0.0 # via documenteer -sphinxcontrib-qthelp==2.0.0 \ - --hash=sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab \ - --hash=sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb +sphinxcontrib-qthelp==2.0.0 # via sphinx -sphinxcontrib-redoc==1.6.0 \ - --hash=sha256:e358edbe23927d36432dde748e978cf897283a331a03e93d3ef02e348dee4561 +sphinxcontrib-redoc==1.6.0 # via documenteer -sphinxcontrib-serializinghtml==2.0.0 \ - --hash=sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331 \ - --hash=sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d +sphinxcontrib-serializinghtml==2.0.0 # via sphinx -sphinxcontrib-youtube==1.4.1 \ - --hash=sha256:de9cb454f066d580a1e7ad64efae7dd9e12c1b1567a31faa330b1aeaeed40460 \ - --hash=sha256:eb7871c8af47fd2b5c9727615354b7f95bce554be8be45b9fa8e5bc022f88059 +sphinxcontrib-youtube==1.4.1 # via documenteer -sphinxext-opengraph==0.9.1 \ - --hash=sha256:b3b230cc6a5b5189139df937f0d9c7b23c7c204493b22646273687969dcb760e \ - --hash=sha256:dd2868a1e7c9497977fbbf44cc0844a42af39ca65fe1bb0272518af225d06fc5 +sphinxext-opengraph==0.9.1 # via documenteer -sphinxext-rediraffe==0.2.7 \ - --hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d \ - --hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c +sphinxext-rediraffe==0.2.7 # via documenteer -sqlalchemy==2.0.32 \ - --hash=sha256:01438ebcdc566d58c93af0171c74ec28efe6a29184b773e378a385e6215389da \ - --hash=sha256:0c1c9b673d21477cec17ab10bc4decb1322843ba35b481585facd88203754fc5 \ - --hash=sha256:0c9045ecc2e4db59bfc97b20516dfdf8e41d910ac6fb667ebd3a79ea54084619 \ - --hash=sha256:0d322cc9c9b2154ba7e82f7bf25ecc7c36fbe2d82e2933b3642fc095a52cfc78 \ - --hash=sha256:0ef18a84e5116340e38eca3e7f9eeaaef62738891422e7c2a0b80feab165905f \ - --hash=sha256:1467940318e4a860afd546ef61fefb98a14d935cd6817ed07a228c7f7c62f389 \ - --hash=sha256:14e09e083a5796d513918a66f3d6aedbc131e39e80875afe81d98a03312889e6 \ - --hash=sha256:167e7497035c303ae50651b351c28dc22a40bb98fbdb8468cdc971821b1ae533 \ - --hash=sha256:19d98f4f58b13900d8dec4ed09dd09ef292208ee44cc9c2fe01c1f0a2fe440e9 \ - --hash=sha256:21b053be28a8a414f2ddd401f1be8361e41032d2ef5884b2f31d31cb723e559f \ - --hash=sha256:251f0d1108aab8ea7b9aadbd07fb47fb8e3a5838dde34aa95a3349876b5a1f1d \ - --hash=sha256:295ff8689544f7ee7e819529633d058bd458c1fd7f7e3eebd0f9268ebc56c2a0 \ - --hash=sha256:2b6be53e4fde0065524f1a0a7929b10e9280987b320716c1509478b712a7688c \ - --hash=sha256:306fe44e754a91cd9d600a6b070c1f2fadbb4a1a257b8781ccf33c7067fd3e4d \ - --hash=sha256:31983018b74908ebc6c996a16ad3690301a23befb643093fcfe85efd292e384d \ - --hash=sha256:328429aecaba2aee3d71e11f2477c14eec5990fb6d0e884107935f7fb6001632 \ - --hash=sha256:3bd1cae7519283ff525e64645ebd7a3e0283f3c038f461ecc1c7b040a0c932a1 \ - --hash=sha256:3cd33c61513cb1b7371fd40cf221256456d26a56284e7d19d1f0b9f1eb7dd7e8 \ - --hash=sha256:3eb6a97a1d39976f360b10ff208c73afb6a4de86dd2a6212ddf65c4a6a2347d5 \ - --hash=sha256:4363ed245a6231f2e2957cccdda3c776265a75851f4753c60f3004b90e69bfeb \ - --hash=sha256:4488120becf9b71b3ac718f4138269a6be99a42fe023ec457896ba4f80749525 \ - --hash=sha256:49496b68cd190a147118af585173ee624114dfb2e0297558c460ad7495f9dfe2 \ - --hash=sha256:4979dc80fbbc9d2ef569e71e0896990bc94df2b9fdbd878290bd129b65ab579c \ - --hash=sha256:52fec964fba2ef46476312a03ec8c425956b05c20220a1a03703537824b5e8e1 \ - --hash=sha256:5954463675cb15db8d4b521f3566a017c8789222b8316b1e6934c811018ee08b \ - --hash=sha256:62e23d0ac103bcf1c5555b6c88c114089587bc64d048fef5bbdb58dfd26f96da \ - --hash=sha256:6bab3db192a0c35e3c9d1560eb8332463e29e5507dbd822e29a0a3c48c0a8d92 \ - --hash=sha256:6c742be912f57586ac43af38b3848f7688863a403dfb220193a882ea60e1ec3a \ - --hash=sha256:723a40ee2cc7ea653645bd4cf024326dea2076673fc9d3d33f20f6c81db83e1d \ - --hash=sha256:78c03d0f8a5ab4f3034c0e8482cfcc415a3ec6193491cfa1c643ed707d476f16 \ - --hash=sha256:7d6ba0497c1d066dd004e0f02a92426ca2df20fac08728d03f67f6960271feec \ - --hash=sha256:7dd8583df2f98dea28b5cd53a1beac963f4f9d087888d75f22fcc93a07cf8d84 \ - --hash=sha256:85a01b5599e790e76ac3fe3aa2f26e1feba56270023d6afd5550ed63c68552b3 \ - --hash=sha256:8a37e4d265033c897892279e8adf505c8b6b4075f2b40d77afb31f7185cd6ecd \ - --hash=sha256:8bd63d051f4f313b102a2af1cbc8b80f061bf78f3d5bd0843ff70b5859e27924 \ - --hash=sha256:916a798f62f410c0b80b63683c8061f5ebe237b0f4ad778739304253353bc1cb \ - --hash=sha256:9365a3da32dabd3e69e06b972b1ffb0c89668994c7e8e75ce21d3e5e69ddef28 \ - --hash=sha256:99db65e6f3ab42e06c318f15c98f59a436f1c78179e6a6f40f529c8cc7100b22 \ - --hash=sha256:aaf04784797dcdf4c0aa952c8d234fa01974c4729db55c45732520ce12dd95b4 \ - --hash=sha256:acd9b73c5c15f0ec5ce18128b1fe9157ddd0044abc373e6ecd5ba376a7e5d961 \ - --hash=sha256:ada0102afff4890f651ed91120c1120065663506b760da4e7823913ebd3258be \ - --hash=sha256:b178e875a7a25b5938b53b006598ee7645172fccafe1c291a706e93f48499ff5 \ - --hash=sha256:b27dfb676ac02529fb6e343b3a482303f16e6bc3a4d868b73935b8792edb52d0 \ - --hash=sha256:b8afd5b26570bf41c35c0121801479958b4446751a3971fb9a480c1afd85558e \ - --hash=sha256:bf2360a5e0f7bd75fa80431bf8ebcfb920c9f885e7956c7efde89031695cafb8 \ - --hash=sha256:c1b88cc8b02b6a5f0efb0345a03672d4c897dc7d92585176f88c67346f565ea8 \ - --hash=sha256:c41a2b9ca80ee555decc605bd3c4520cc6fef9abde8fd66b1cf65126a6922d65 \ - --hash=sha256:c750987fc876813f27b60d619b987b057eb4896b81117f73bb8d9918c14f1cad \ - --hash=sha256:e567a8793a692451f706b363ccf3c45e056b67d90ead58c3bc9471af5d212202 +sqlalchemy==2.0.36 # via jupyter-cache -stack-data==0.6.3 \ - --hash=sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9 \ - --hash=sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695 +stack-data==0.6.3 # via ipython -tabulate==0.9.0 \ - --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \ - --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f +tabulate==0.9.0 # via jupyter-cache -termcolor==2.4.0 \ - --hash=sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63 \ - --hash=sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a +termcolor==2.5.0 # via pytest-sugar -tomlkit==0.13.2 \ - --hash=sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde \ - --hash=sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79 +tomlkit==0.13.2 # via documenteer -tornado==6.4.1 \ - --hash=sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8 \ - --hash=sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f \ - --hash=sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4 \ - --hash=sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3 \ - --hash=sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14 \ - --hash=sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842 \ - --hash=sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9 \ - --hash=sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698 \ - --hash=sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7 \ - --hash=sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d \ - --hash=sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4 +tornado==6.4.1 # via # ipykernel # jupyter-client -traitlets==5.14.3 \ - --hash=sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7 \ - --hash=sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f +traitlets==5.14.3 # via # comm # ipykernel @@ -1388,17 +402,11 @@ traitlets==5.14.3 \ # matplotlib-inline # nbclient # nbformat -types-pyyaml==6.0.12.20240808 \ - --hash=sha256:b8f76ddbd7f65440a8bda5526a9607e4c7a322dc2f8e1a8c405644f9a6f4b9af \ - --hash=sha256:deda34c5c655265fc517b546c902aa6eed2ef8d3e921e4765fe606fe2afe8d35 +types-pyyaml==6.0.12.20240917 # via -r requirements/dev.in -types-requests==2.32.0.20240712 \ - --hash=sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358 \ - --hash=sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3 +types-requests==2.32.0.20241016 # via -r requirements/dev.in -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 +typing-extensions==4.12.2 # via # -c requirements/main.txt # mypy @@ -1406,28 +414,18 @@ typing-extensions==4.12.2 \ # pydantic # pydantic-core # sqlalchemy -uc-micro-py==1.0.3 \ - --hash=sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a \ - --hash=sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5 +uc-micro-py==1.0.3 # via linkify-it-py -urllib3==2.2.2 \ - --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ - --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 +urllib3==2.2.3 # via # -c requirements/main.txt # documenteer # requests # sphinx-prompt # types-requests -virtualenv==20.26.3 \ - --hash=sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a \ - --hash=sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589 +virtualenv==20.26.6 # via pre-commit -wcwidth==0.2.13 \ - --hash=sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859 \ - --hash=sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5 +wcwidth==0.2.13 # via prompt-toolkit -zipp==3.20.1 \ - --hash=sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064 \ - --hash=sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b +zipp==3.20.2 # via importlib-metadata diff --git a/requirements/main.in b/requirements/main.in index caff10de..78536644 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -18,9 +18,16 @@ httpx httpx-sse jinja2 pydantic>2 -pydantic-settings +pydantic-settings==2.5.2 +# 2.6 dies horribly with: +# pydantic_core._pydantic_core.ValidationError: 1 validation error for GitHubRefreshAppConfig +# MOBU_GITHUB_REFRESH_APP_WEBHOOK_SECRET +# Field required [type=missing, input_value={'accepted_github_orgs': ...', 'org2', 'lsst-sqre']}, input_type=dict] +# For further information visit https://errors.pydantic.dev/2.9/v/missing pyvo pyyaml +rubin-nublado-client@git+https://github.com/lsst-sqre/nublado@tickets/DM-46480#subdirectory=client +#rubin.nublado.client safir>=6.1.0 shortuuid structlog diff --git a/requirements/main.txt b/requirements/main.txt index ee0ab62a..db89dabb 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -1,915 +1,157 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes --output-file requirements/main.txt requirements/main.in -aiojobs==1.3.0 \ - --hash=sha256:03074c884b3dc388b8d798c0de24ec17d72b2799018497fda8062c0431a494b5 \ - --hash=sha256:1f9f36179b6d50796c4fc9e8851fdae10f38d6c2f64412a91e2c4eff73054ce0 +# uv pip compile --output-file requirements/main.txt requirements/main.in +aiojobs==1.3.0 # via -r requirements/main.in -annotated-types==0.7.0 \ - --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ - --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 +annotated-types==0.7.0 # via pydantic -anyio==4.4.0 \ - --hash=sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94 \ - --hash=sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7 +anyio==4.6.2.post1 # via # httpx # starlette # watchfiles -astropy==6.1.2 \ - --hash=sha256:04eead3eb28021a5853edb620ed6f50311bd5d272ccad06ed82fee293441a834 \ - --hash=sha256:075c54dd9cd8eab52b2de4eddddec0543dfaf7879c231a811b9ba872514f87f6 \ - --hash=sha256:12d292909a86f00316c9d3007ae8991906c23461400dba1cb6de63ad55449a32 \ - --hash=sha256:18747bae9a1eee0e5a408907b82219ddc356198de0948a80bb7d27143e780b7d \ - --hash=sha256:2d35bf528e8bc6b0f87db9d7ade428964bab51b7bbcf0f11ad3790fa60fcb279 \ - --hash=sha256:2e25057dd6b5fd8f543f2d08f46fcf6a3691135231f1c016da477df22a25e13b \ - --hash=sha256:305433b7571d3dbcbc264dbf96ec334a89836ddd78d0d15f77821b90eef3f7b4 \ - --hash=sha256:4563a6d5643c321acb508792ccbec5f1c62302e3271109229ab023d69902a712 \ - --hash=sha256:4aaa06dc984ff3e409019a51935ac9c31875baa538de04c1634ab02f727dd52b \ - --hash=sha256:4f80865e18ffbe2f9901e59e6f750218b823b5c591f687c2bca3adf0f2a6af4e \ - --hash=sha256:5f8cbd0e3d4b17715e508de2ef0f84057a810b3724b6219181f49d726c1d6436 \ - --hash=sha256:8a32996e01553ba5469c0cebf9d7f6587ed11d691f88a0d0879b4ab0609e8f7f \ - --hash=sha256:8bd518b0c94c48a74e95d8b949bd50bf6f72cf1dd56ed925c19c689a39aaaab4 \ - --hash=sha256:8f846339fdd093b261dc33a85a78eafa04598b4d8f1807a18ceb0f6eb9a097ef \ - --hash=sha256:99b1d4cb739ff5c20a76e4c42ed38478a8fbd8482fada504796e0d55d39cb5bd \ - --hash=sha256:a2103d4e24e90389a820cfcdaaf4ca2d1ab22e5fd72978d147ff5cace54f1d3a \ - --hash=sha256:a64eb948c8c1c87545592ff5e3ba366f3a71615dea6532a96891874b03bd9a5d \ - --hash=sha256:b2521be1a1e76c92444905da84cee541e052408632d7fc1fb853e57ef5190963 \ - --hash=sha256:c39fcd493753e4f3628ee775171611fc1c0cc419bc61f7fe69b84ec02b117a54 \ - --hash=sha256:c50fa9dcd4fbafd54c5da15092f8d9200b2c82711f8971dd23c139920c6c780c \ - --hash=sha256:d959819a695083f0653e0b28c661f4388fdb0c812ccc3f5c343626ec5a1708e5 \ - --hash=sha256:f5ee7e334a0601858fcd4b72490b0626174ac97fd591fc3408b496d20167f186 +astropy==6.1.4 # via pyvo -astropy-iers-data==0.2024.8.27.10.28.29 \ - --hash=sha256:13fd9e56103803d85467a0d20009268a7d0e812c6828ca5e04e11a5b779ecd65 \ - --hash=sha256:8102e74d67d03b569f3da47aad3f410ce15d195110c479c38b61bf76e1aed516 +astropy-iers-data==0.2024.10.14.0.32.55 # via astropy -certifi==2024.7.4 \ - --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ - --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 +certifi==2024.8.30 # via # httpcore # httpx # requests -cffi==1.17.0 \ - --hash=sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f \ - --hash=sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab \ - --hash=sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499 \ - --hash=sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058 \ - --hash=sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693 \ - --hash=sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb \ - --hash=sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377 \ - --hash=sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885 \ - --hash=sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2 \ - --hash=sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401 \ - --hash=sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4 \ - --hash=sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b \ - --hash=sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59 \ - --hash=sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f \ - --hash=sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c \ - --hash=sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555 \ - --hash=sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa \ - --hash=sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424 \ - --hash=sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb \ - --hash=sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2 \ - --hash=sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8 \ - --hash=sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e \ - --hash=sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9 \ - --hash=sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82 \ - --hash=sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828 \ - --hash=sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759 \ - --hash=sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc \ - --hash=sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118 \ - --hash=sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf \ - --hash=sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932 \ - --hash=sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a \ - --hash=sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29 \ - --hash=sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206 \ - --hash=sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2 \ - --hash=sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c \ - --hash=sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c \ - --hash=sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0 \ - --hash=sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a \ - --hash=sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195 \ - --hash=sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6 \ - --hash=sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9 \ - --hash=sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc \ - --hash=sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb \ - --hash=sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0 \ - --hash=sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7 \ - --hash=sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb \ - --hash=sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a \ - --hash=sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492 \ - --hash=sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720 \ - --hash=sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42 \ - --hash=sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7 \ - --hash=sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d \ - --hash=sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d \ - --hash=sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb \ - --hash=sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4 \ - --hash=sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2 \ - --hash=sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b \ - --hash=sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8 \ - --hash=sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e \ - --hash=sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204 \ - --hash=sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3 \ - --hash=sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150 \ - --hash=sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4 \ - --hash=sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76 \ - --hash=sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e \ - --hash=sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb \ - --hash=sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91 +cffi==1.17.1 # via cryptography -charset-normalizer==3.3.2 \ - --hash=sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027 \ - --hash=sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087 \ - --hash=sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786 \ - --hash=sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8 \ - --hash=sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09 \ - --hash=sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185 \ - --hash=sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574 \ - --hash=sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e \ - --hash=sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519 \ - --hash=sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898 \ - --hash=sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269 \ - --hash=sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3 \ - --hash=sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f \ - --hash=sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6 \ - --hash=sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8 \ - --hash=sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a \ - --hash=sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73 \ - --hash=sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc \ - --hash=sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714 \ - --hash=sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2 \ - --hash=sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc \ - --hash=sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce \ - --hash=sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d \ - --hash=sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e \ - --hash=sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6 \ - --hash=sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269 \ - --hash=sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96 \ - --hash=sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d \ - --hash=sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a \ - --hash=sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4 \ - --hash=sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77 \ - --hash=sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d \ - --hash=sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0 \ - --hash=sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed \ - --hash=sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068 \ - --hash=sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac \ - --hash=sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25 \ - --hash=sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8 \ - --hash=sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab \ - --hash=sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26 \ - --hash=sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2 \ - --hash=sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db \ - --hash=sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f \ - --hash=sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5 \ - --hash=sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99 \ - --hash=sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c \ - --hash=sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d \ - --hash=sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811 \ - --hash=sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa \ - --hash=sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a \ - --hash=sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03 \ - --hash=sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b \ - --hash=sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04 \ - --hash=sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c \ - --hash=sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001 \ - --hash=sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458 \ - --hash=sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389 \ - --hash=sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99 \ - --hash=sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985 \ - --hash=sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537 \ - --hash=sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238 \ - --hash=sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f \ - --hash=sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d \ - --hash=sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796 \ - --hash=sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a \ - --hash=sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143 \ - --hash=sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8 \ - --hash=sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c \ - --hash=sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5 \ - --hash=sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5 \ - --hash=sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711 \ - --hash=sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4 \ - --hash=sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6 \ - --hash=sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c \ - --hash=sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7 \ - --hash=sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4 \ - --hash=sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b \ - --hash=sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae \ - --hash=sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12 \ - --hash=sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c \ - --hash=sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae \ - --hash=sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8 \ - --hash=sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887 \ - --hash=sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b \ - --hash=sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4 \ - --hash=sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f \ - --hash=sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5 \ - --hash=sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33 \ - --hash=sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519 \ - --hash=sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561 +charset-normalizer==3.4.0 # via requests -click==8.1.7 \ - --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ - --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de +click==8.1.7 # via # -r requirements/main.in # safir # uvicorn -cryptography==43.0.0 \ - --hash=sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709 \ - --hash=sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069 \ - --hash=sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2 \ - --hash=sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b \ - --hash=sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e \ - --hash=sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70 \ - --hash=sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778 \ - --hash=sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22 \ - --hash=sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895 \ - --hash=sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf \ - --hash=sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431 \ - --hash=sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f \ - --hash=sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947 \ - --hash=sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74 \ - --hash=sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc \ - --hash=sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66 \ - --hash=sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66 \ - --hash=sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf \ - --hash=sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f \ - --hash=sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5 \ - --hash=sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e \ - --hash=sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f \ - --hash=sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55 \ - --hash=sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1 \ - --hash=sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47 \ - --hash=sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5 \ - --hash=sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0 +cryptography==43.0.1 # via # pyjwt # safir -fastapi==0.112.2 \ - --hash=sha256:3d4729c038414d5193840706907a41839d839523da6ed0c2811f1168cac1798c \ - --hash=sha256:db84b470bd0e2b1075942231e90e3577e12a903c4dc8696f0d206a7904a7af1c +fastapi==0.115.2 # via # -r requirements/main.in # safir -gidgethub==5.3.0 \ - --hash=sha256:4dd92f2252d12756b13f9dd15cde322bfb0d625b6fb5d680da1567ec74b462c0 \ - --hash=sha256:9ece7d37fbceb819b80560e7ed58f936e48a65d37ec5f56db79145156b426a25 +gidgethub==5.3.0 # via # -r requirements/main.in # safir -h11==0.14.0 \ - --hash=sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d \ - --hash=sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761 +greenlet==3.1.1 + # via rubin-nublado-client +h11==0.14.0 # via # httpcore # uvicorn -httpcore==1.0.5 \ - --hash=sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61 \ - --hash=sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5 +httpcore==1.0.6 # via httpx -httptools==0.6.1 \ - --hash=sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563 \ - --hash=sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142 \ - --hash=sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d \ - --hash=sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b \ - --hash=sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4 \ - --hash=sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb \ - --hash=sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658 \ - --hash=sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084 \ - --hash=sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2 \ - --hash=sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97 \ - --hash=sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837 \ - --hash=sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3 \ - --hash=sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58 \ - --hash=sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da \ - --hash=sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d \ - --hash=sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90 \ - --hash=sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0 \ - --hash=sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1 \ - --hash=sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2 \ - --hash=sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e \ - --hash=sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0 \ - --hash=sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf \ - --hash=sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc \ - --hash=sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3 \ - --hash=sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503 \ - --hash=sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a \ - --hash=sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3 \ - --hash=sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949 \ - --hash=sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84 \ - --hash=sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb \ - --hash=sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a \ - --hash=sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f \ - --hash=sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e \ - --hash=sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81 \ - --hash=sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185 \ - --hash=sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3 +httptools==0.6.4 # via uvicorn -httpx==0.27.2 \ - --hash=sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0 \ - --hash=sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2 +httpx==0.27.2 # via # -r requirements/main.in + # rubin-nublado-client # safir -httpx-sse==0.4.0 \ - --hash=sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721 \ - --hash=sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f - # via -r requirements/main.in -idna==3.8 \ - --hash=sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac \ - --hash=sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603 +httpx-sse==0.4.0 + # via + # -r requirements/main.in + # rubin-nublado-client +idna==3.10 # via # anyio # httpx # requests -jinja2==3.1.4 \ - --hash=sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369 \ - --hash=sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d +jinja2==3.1.4 # via -r requirements/main.in -markupsafe==2.1.5 \ - --hash=sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf \ - --hash=sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff \ - --hash=sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f \ - --hash=sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3 \ - --hash=sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532 \ - --hash=sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f \ - --hash=sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617 \ - --hash=sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df \ - --hash=sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4 \ - --hash=sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906 \ - --hash=sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f \ - --hash=sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4 \ - --hash=sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8 \ - --hash=sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371 \ - --hash=sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2 \ - --hash=sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465 \ - --hash=sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52 \ - --hash=sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6 \ - --hash=sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169 \ - --hash=sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad \ - --hash=sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2 \ - --hash=sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0 \ - --hash=sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029 \ - --hash=sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f \ - --hash=sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a \ - --hash=sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced \ - --hash=sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5 \ - --hash=sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c \ - --hash=sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf \ - --hash=sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9 \ - --hash=sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb \ - --hash=sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad \ - --hash=sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3 \ - --hash=sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1 \ - --hash=sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46 \ - --hash=sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc \ - --hash=sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a \ - --hash=sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee \ - --hash=sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900 \ - --hash=sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5 \ - --hash=sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea \ - --hash=sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f \ - --hash=sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5 \ - --hash=sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e \ - --hash=sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a \ - --hash=sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f \ - --hash=sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50 \ - --hash=sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a \ - --hash=sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b \ - --hash=sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4 \ - --hash=sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff \ - --hash=sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2 \ - --hash=sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46 \ - --hash=sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b \ - --hash=sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf \ - --hash=sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5 \ - --hash=sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5 \ - --hash=sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab \ - --hash=sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd \ - --hash=sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68 +markupsafe==3.0.1 # via jinja2 -numpy==2.1.0 \ - --hash=sha256:08801848a40aea24ce16c2ecde3b756f9ad756586fb2d13210939eb69b023f5b \ - --hash=sha256:0937e54c09f7a9a68da6889362ddd2ff584c02d015ec92672c099b61555f8911 \ - --hash=sha256:0ab32eb9170bf8ffcbb14f11613f4a0b108d3ffee0832457c5d4808233ba8977 \ - --hash=sha256:0abb3916a35d9090088a748636b2c06dc9a6542f99cd476979fb156a18192b84 \ - --hash=sha256:0af3a5987f59d9c529c022c8c2a64805b339b7ef506509fba7d0556649b9714b \ - --hash=sha256:10e2350aea18d04832319aac0f887d5fcec1b36abd485d14f173e3e900b83e33 \ - --hash=sha256:15ef8b2177eeb7e37dd5ef4016f30b7659c57c2c0b57a779f1d537ff33a72c7b \ - --hash=sha256:1f817c71683fd1bb5cff1529a1d085a57f02ccd2ebc5cd2c566f9a01118e3b7d \ - --hash=sha256:24003ba8ff22ea29a8c306e61d316ac74111cebf942afbf692df65509a05f111 \ - --hash=sha256:30014b234f07b5fec20f4146f69e13cfb1e33ee9a18a1879a0142fbb00d47673 \ - --hash=sha256:343e3e152bf5a087511cd325e3b7ecfd5b92d369e80e74c12cd87826e263ec06 \ - --hash=sha256:378cb4f24c7d93066ee4103204f73ed046eb88f9ad5bb2275bb9fa0f6a02bd36 \ - --hash=sha256:398049e237d1aae53d82a416dade04defed1a47f87d18d5bd615b6e7d7e41d1f \ - --hash=sha256:3a3336fbfa0d38d3deacd3fe7f3d07e13597f29c13abf4d15c3b6dc2291cbbdd \ - --hash=sha256:442596f01913656d579309edcd179a2a2f9977d9a14ff41d042475280fc7f34e \ - --hash=sha256:44e44973262dc3ae79e9063a1284a73e09d01b894b534a769732ccd46c28cc62 \ - --hash=sha256:54139e0eb219f52f60656d163cbe67c31ede51d13236c950145473504fa208cb \ - --hash=sha256:5474dad8c86ee9ba9bb776f4b99ef2d41b3b8f4e0d199d4f7304728ed34d0300 \ - --hash=sha256:54c6a63e9d81efe64bfb7bcb0ec64332a87d0b87575f6009c8ba67ea6374770b \ - --hash=sha256:624884b572dff8ca8f60fab591413f077471de64e376b17d291b19f56504b2bb \ - --hash=sha256:6326ab99b52fafdcdeccf602d6286191a79fe2fda0ae90573c5814cd2b0bc1b8 \ - --hash=sha256:652e92fc409e278abdd61e9505649e3938f6d04ce7ef1953f2ec598a50e7c195 \ - --hash=sha256:6c1de77ded79fef664d5098a66810d4d27ca0224e9051906e634b3f7ead134c2 \ - --hash=sha256:76368c788ccb4f4782cf9c842b316140142b4cbf22ff8db82724e82fe1205dce \ - --hash=sha256:7a894c51fd8c4e834f00ac742abad73fc485df1062f1b875661a3c1e1fb1c2f6 \ - --hash=sha256:7dc90da0081f7e1da49ec4e398ede6a8e9cc4f5ebe5f9e06b443ed889ee9aaa2 \ - --hash=sha256:848c6b5cad9898e4b9ef251b6f934fa34630371f2e916261070a4eb9092ffd33 \ - --hash=sha256:899da829b362ade41e1e7eccad2cf274035e1cb36ba73034946fccd4afd8606b \ - --hash=sha256:8ab81ccd753859ab89e67199b9da62c543850f819993761c1e94a75a814ed667 \ - --hash=sha256:8fb49a0ba4d8f41198ae2d52118b050fd34dace4b8f3fb0ee34e23eb4ae775b1 \ - --hash=sha256:9156ca1f79fc4acc226696e95bfcc2b486f165a6a59ebe22b2c1f82ab190384a \ - --hash=sha256:9523f8b46485db6939bd069b28b642fec86c30909cea90ef550373787f79530e \ - --hash=sha256:a0756a179afa766ad7cb6f036de622e8a8f16ffdd55aa31f296c870b5679d745 \ - --hash=sha256:a0cdef204199278f5c461a0bed6ed2e052998276e6d8ab2963d5b5c39a0500bc \ - --hash=sha256:ab83adc099ec62e044b1fbb3a05499fa1e99f6d53a1dde102b2d85eff66ed324 \ - --hash=sha256:b34fa5e3b5d6dc7e0a4243fa0f81367027cb6f4a7215a17852979634b5544ee0 \ - --hash=sha256:b47c551c6724960479cefd7353656498b86e7232429e3a41ab83be4da1b109e8 \ - --hash=sha256:c4cd94dfefbefec3f8b544f61286584292d740e6e9d4677769bc76b8f41deb02 \ - --hash=sha256:c4f982715e65036c34897eb598d64aef15150c447be2cfc6643ec7a11af06574 \ - --hash=sha256:d8f699a709120b220dfe173f79c73cb2a2cab2c0b88dd59d7b49407d032b8ebd \ - --hash=sha256:dd94ce596bda40a9618324547cfaaf6650b1a24f5390350142499aa4e34e53d1 \ - --hash=sha256:de844aaa4815b78f6023832590d77da0e3b6805c644c33ce94a1e449f16d6ab5 \ - --hash=sha256:e5f0642cdf4636198a4990de7a71b693d824c56a757862230454629cf62e323d \ - --hash=sha256:f07fa2f15dabe91259828ce7d71b5ca9e2eb7c8c26baa822c825ce43552f4883 \ - --hash=sha256:f15976718c004466406342789f31b6673776360f3b1e3c575f25302d7e789575 \ - --hash=sha256:f358ea9e47eb3c2d6eba121ab512dfff38a88db719c38d1e67349af210bc7529 \ - --hash=sha256:f505264735ee074250a9c78247ee8618292091d9d1fcc023290e9ac67e8f1afa \ - --hash=sha256:f5ebbf9fbdabed208d4ecd2e1dfd2c0741af2f876e7ae522c2537d404ca895c3 \ - --hash=sha256:f6b26e6c3b98adb648243670fddc8cab6ae17473f9dc58c51574af3e64d61211 \ - --hash=sha256:f8e93a01a35be08d31ae33021e5268f157a2d60ebd643cfc15de6ab8e4722eb1 \ - --hash=sha256:fe76d75b345dc045acdbc006adcb197cc680754afd6c259de60d358d60c93736 \ - --hash=sha256:ffbd6faeb190aaf2b5e9024bac9622d2ee549b7ec89ef3a9373fa35313d44e0e +numpy==2.1.2 # via # astropy # pyerfa -packaging==24.1 \ - --hash=sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002 \ - --hash=sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124 +packaging==24.1 # via astropy -pycparser==2.22 \ - --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ - --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc +pycparser==2.22 # via cffi -pydantic==2.8.2 \ - --hash=sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a \ - --hash=sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8 +pydantic==2.9.2 # via # -r requirements/main.in # fastapi # pydantic-settings + # rubin-nublado-client # safir -pydantic-core==2.20.1 \ - --hash=sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d \ - --hash=sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f \ - --hash=sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686 \ - --hash=sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482 \ - --hash=sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006 \ - --hash=sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83 \ - --hash=sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6 \ - --hash=sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88 \ - --hash=sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86 \ - --hash=sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a \ - --hash=sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6 \ - --hash=sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a \ - --hash=sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6 \ - --hash=sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6 \ - --hash=sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43 \ - --hash=sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c \ - --hash=sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4 \ - --hash=sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e \ - --hash=sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203 \ - --hash=sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd \ - --hash=sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1 \ - --hash=sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24 \ - --hash=sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc \ - --hash=sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc \ - --hash=sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3 \ - --hash=sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598 \ - --hash=sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98 \ - --hash=sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331 \ - --hash=sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2 \ - --hash=sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a \ - --hash=sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6 \ - --hash=sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688 \ - --hash=sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91 \ - --hash=sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa \ - --hash=sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b \ - --hash=sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0 \ - --hash=sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840 \ - --hash=sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c \ - --hash=sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd \ - --hash=sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3 \ - --hash=sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231 \ - --hash=sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1 \ - --hash=sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953 \ - --hash=sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250 \ - --hash=sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a \ - --hash=sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2 \ - --hash=sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20 \ - --hash=sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434 \ - --hash=sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab \ - --hash=sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703 \ - --hash=sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a \ - --hash=sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2 \ - --hash=sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac \ - --hash=sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611 \ - --hash=sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121 \ - --hash=sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e \ - --hash=sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b \ - --hash=sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09 \ - --hash=sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906 \ - --hash=sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9 \ - --hash=sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7 \ - --hash=sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b \ - --hash=sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987 \ - --hash=sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c \ - --hash=sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b \ - --hash=sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e \ - --hash=sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237 \ - --hash=sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1 \ - --hash=sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19 \ - --hash=sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b \ - --hash=sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad \ - --hash=sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0 \ - --hash=sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94 \ - --hash=sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312 \ - --hash=sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f \ - --hash=sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669 \ - --hash=sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1 \ - --hash=sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe \ - --hash=sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99 \ - --hash=sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a \ - --hash=sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a \ - --hash=sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52 \ - --hash=sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c \ - --hash=sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad \ - --hash=sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1 \ - --hash=sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a \ - --hash=sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f \ - --hash=sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a \ - --hash=sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27 +pydantic-core==2.23.4 # via # pydantic # safir -pydantic-settings==2.4.0 \ - --hash=sha256:bb6849dc067f1687574c12a639e231f3a6feeed0a12d710c1382045c5db1c315 \ - --hash=sha256:ed81c3a0f46392b4d7c0a565c05884e6e54b3456e6f0fe4d8814981172dc9a88 - # via -r requirements/main.in -pyerfa==2.0.1.4 \ - --hash=sha256:39cf838c9a21e40d4e3183bead65b3ce6af763c4a727f87d84909c9be7d3a33c \ - --hash=sha256:46d3bed0ac666f08d8364b34a00b8c6595358d6c4f4532da8d13fac0e5227baa \ - --hash=sha256:610d2bc314e140d876b93b1287c7c81685434873c8700cc3e1596193f77d1071 \ - --hash=sha256:7e4508dd7ffd7b27b7f67168643764454887e990ca9e4584824f0e3ab5884c0f \ - --hash=sha256:83a44ba84ebfc3244412ecbf1065c087c382da84f1c3eee1f2a0638d9046ac96 \ - --hash=sha256:88a8d0f3608a66871615bd168fcddf674dce9f7568c239a03cf8d9936161d032 \ - --hash=sha256:900b266a3862baa9560d6b1b184dcc14e0e76d550ff70d32336d3989b2ed18ca \ - --hash=sha256:9045e9f786c76cb55da86ada3405c378c32b88f6e3c6296cb288496ab374b068 \ - --hash=sha256:acb8a6713232ea35c04bc6e40ac4e461dfcc817d395ef2a3c8051c1a33249dd3 \ - --hash=sha256:bc3cf45967ac1af77a777deb050fb08bbc75256dd97ca6005e4d385358b7af40 \ - --hash=sha256:ff112353944bf705342741f2fe41674f97154a302b0295eaef7381af92ad2b3a +pydantic-settings==2.5.2 + # via + # -r requirements/main.in + # rubin-nublado-client +pyerfa==2.0.1.4 # via astropy -pyjwt==2.9.0 \ - --hash=sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850 \ - --hash=sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c +pyjwt==2.9.0 # via gidgethub -python-dotenv==1.0.1 \ - --hash=sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca \ - --hash=sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a +python-dotenv==1.0.1 # via # pydantic-settings # uvicorn -pyvo==1.5.2 \ - --hash=sha256:b8a24c44dace5c607b1d93afd0257d15fa109f1e865772347ea949eea01c8f71 \ - --hash=sha256:f4306d4e8f21c604dbd5df65ce101101633d62d854b0fc9c7746f342877e99f6 +pyvo==1.5.3 # via -r requirements/main.in -pyyaml==6.0.2 \ - --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ - --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ - --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ - --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ - --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ - --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ - --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ - --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ - --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ - --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ - --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ - --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ - --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ - --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ - --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ - --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ - --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ - --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ - --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ - --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ - --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ - --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ - --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ - --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ - --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ - --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ - --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ - --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ - --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ - --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ - --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ - --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ - --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ - --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ - --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ - --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ - --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ - --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ - --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ - --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ - --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ - --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ - --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ - --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ - --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ - --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ - --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ - --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ - --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ - --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ - --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ - --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ - --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 +pyyaml==6.0.2 # via # -r requirements/main.in # astropy + # rubin-nublado-client # uvicorn -requests==2.32.3 \ - --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ - --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 +requests==2.32.3 # via pyvo -safir==6.3.0 \ - --hash=sha256:2fcd64bf37dd42eacedd6378341b2487cd06dbaf1f28403301b8d80f60a4fb56 \ - --hash=sha256:6ad7dad520d87d853628849ef95a348c55dbd0180ad3f15c1cf2f7f8fe32f915 +rubin-nublado-client @ git+https://github.com/lsst-sqre/nublado@5b90668c62d41df1201ee6319dbc3e412ceea9b9#subdirectory=client # via -r requirements/main.in -safir-logging==6.3.0 \ - --hash=sha256:491dfe85de89a3f2daa29c491a22a0551f0961444490418d91ec50c040ae16eb \ - --hash=sha256:e14754ab0bba6cfa248c3fc4cb5ca28410d97ff3965e831eab6581ed37485e79 +safir==6.4.0 + # via + # -r requirements/main.in + # rubin-nublado-client +safir-logging==6.4.0 # via safir -shortuuid==1.0.13 \ - --hash=sha256:3bb9cf07f606260584b1df46399c0b87dd84773e7b25912b7e391e30797c5e72 \ - --hash=sha256:a482a497300b49b4953e15108a7913244e1bb0d41f9d332f5e9925dba33a3c5a - # via -r requirements/main.in -sniffio==1.3.1 \ - --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ - --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc +shortuuid==1.0.13 + # via + # -r requirements/main.in + # rubin-nublado-client +sniffio==1.3.1 # via # anyio # httpx -starlette==0.38.2 \ - --hash=sha256:4ec6a59df6bbafdab5f567754481657f7ed90dc9d69b0c9ff017907dd54faeff \ - --hash=sha256:c7c0441065252160993a1a37cf2a73bb64d271b17303e0b0c1eb7191cfb12d75 +starlette==0.40.0 # via # -r requirements/main.in # fastapi # safir -structlog==24.4.0 \ - --hash=sha256:597f61e80a91cc0749a9fd2a098ed76715a1c8a01f73e336b746504d1aad7610 \ - --hash=sha256:b27bfecede327a6d2da5fbc96bd859f114ecc398a6389d664f62085ee7ae6fc4 +structlog==24.4.0 # via # -r requirements/main.in + # rubin-nublado-client # safir # safir-logging -typing-extensions==4.12.2 \ - --hash=sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d \ - --hash=sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8 +typing-extensions==4.12.2 # via # fastapi # pydantic # pydantic-core -uritemplate==4.1.1 \ - --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ - --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e +uritemplate==4.1.1 # via gidgethub -urllib3==2.2.2 \ - --hash=sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472 \ - --hash=sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168 +urllib3==2.2.3 # via requests -uvicorn==0.30.6 \ - --hash=sha256:4b15decdda1e72be08209e860a1e10e92439ad5b97cf44cc945fcbee66fc5788 \ - --hash=sha256:65fd46fe3fda5bdc1b03b94eb634923ff18cd35b2f084813ea79d1f103f711b5 +uvicorn==0.32.0 # via -r requirements/main.in -uvloop==0.20.0 \ - --hash=sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847 \ - --hash=sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2 \ - --hash=sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b \ - --hash=sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315 \ - --hash=sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5 \ - --hash=sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469 \ - --hash=sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d \ - --hash=sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf \ - --hash=sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9 \ - --hash=sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab \ - --hash=sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e \ - --hash=sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e \ - --hash=sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0 \ - --hash=sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756 \ - --hash=sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73 \ - --hash=sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006 \ - --hash=sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541 \ - --hash=sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae \ - --hash=sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a \ - --hash=sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996 \ - --hash=sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7 \ - --hash=sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00 \ - --hash=sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b \ - --hash=sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10 \ - --hash=sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95 \ - --hash=sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9 \ - --hash=sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037 \ - --hash=sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6 \ - --hash=sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66 \ - --hash=sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba \ - --hash=sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf +uvloop==0.21.0 # via uvicorn -watchfiles==0.23.0 \ - --hash=sha256:02b7ba9d4557149410747353e7325010d48edcfe9d609a85cb450f17fd50dc3d \ - --hash=sha256:02ff5d7bd066c6a7673b17c8879cd8ee903078d184802a7ee851449c43521bdd \ - --hash=sha256:0e01bcb8d767c58865207a6c2f2792ad763a0fe1119fb0a430f444f5b02a5ea0 \ - --hash=sha256:0eff099a4df36afaa0eea7a913aa64dcf2cbd4e7a4f319a73012210af4d23810 \ - --hash=sha256:109a61763e7318d9f821b878589e71229f97366fa6a5c7720687d367f3ab9eef \ - --hash=sha256:11698bb2ea5e991d10f1f4f83a39a02f91e44e4bd05f01b5c1ec04c9342bf63c \ - --hash=sha256:130a896d53b48a1cecccfa903f37a1d87dbb74295305f865a3e816452f6e49e4 \ - --hash=sha256:1733b9bc2c8098c6bdb0ff7a3d7cb211753fecb7bd99bdd6df995621ee1a574b \ - --hash=sha256:18e2de19801b0eaa4c5292a223effb7cfb43904cb742c5317a0ac686ed604765 \ - --hash=sha256:1cf7f486169986c4b9d34087f08ce56a35126600b6fef3028f19ca16d5889071 \ - --hash=sha256:1d636c8aeb28cdd04a4aa89030c4b48f8b2954d8483e5f989774fa441c0ed57b \ - --hash=sha256:1db691bad0243aed27c8354b12d60e8e266b75216ae99d33e927ff5238d270b5 \ - --hash=sha256:1e5f3ca0ff47940ce0a389457b35d6df601c317c1e1a9615981c474452f98de1 \ - --hash=sha256:1ebaebb53b34690da0936c256c1cdb0914f24fb0e03da76d185806df9328abed \ - --hash=sha256:20b423b58f5fdde704a226b598a2d78165fe29eb5621358fe57ea63f16f165c4 \ - --hash=sha256:2368c5371c17fdcb5a2ea71c5c9d49f9b128821bfee69503cc38eae00feb3220 \ - --hash=sha256:24655e8c1c9c114005c3868a3d432c8aa595a786b8493500071e6a52f3d09217 \ - --hash=sha256:2537ef60596511df79b91613a5bb499b63f46f01a11a81b0a2b0dedf645d0a9c \ - --hash=sha256:296e0b29ab0276ca59d82d2da22cbbdb39a23eed94cca69aed274595fb3dfe42 \ - --hash=sha256:2aec5c29915caf08771d2507da3ac08e8de24a50f746eb1ed295584ba1820330 \ - --hash=sha256:2dddc2487d33e92f8b6222b5fb74ae2cfde5e8e6c44e0248d24ec23befdc5366 \ - --hash=sha256:37fd826dac84c6441615aa3f04077adcc5cac7194a021c9f0d69af20fb9fa788 \ - --hash=sha256:3af1b05361e1cc497bf1be654a664750ae61f5739e4bb094a2be86ec8c6db9b6 \ - --hash=sha256:40cb8fa00028908211eb9f8d47744dca21a4be6766672e1ff3280bee320436f1 \ - --hash=sha256:46f1d8069a95885ca529645cdbb05aea5837d799965676e1b2b1f95a4206313e \ - --hash=sha256:486bda18be5d25ab5d932699ceed918f68eb91f45d018b0343e3502e52866e5e \ - --hash=sha256:48a1b05c0afb2cd2f48c1ed2ae5487b116e34b93b13074ed3c22ad5c743109f0 \ - --hash=sha256:4ccd3011cc7ee2f789af9ebe04745436371d36afe610028921cab9f24bb2987b \ - --hash=sha256:4ea756e425ab2dfc8ef2a0cb87af8aa7ef7dfc6fc46c6f89bcf382121d4fff75 \ - --hash=sha256:524fcb8d59b0dbee2c9b32207084b67b2420f6431ed02c18bd191e6c575f5c48 \ - --hash=sha256:532e1f2c491274d1333a814e4c5c2e8b92345d41b12dc806cf07aaff786beb66 \ - --hash=sha256:556347b0abb4224c5ec688fc58214162e92a500323f50182f994f3ad33385dcb \ - --hash=sha256:62d2b18cb1edaba311fbbfe83fb5e53a858ba37cacb01e69bc20553bb70911b8 \ - --hash=sha256:6991e3a78f642368b8b1b669327eb6751439f9f7eaaa625fae67dd6070ecfa0b \ - --hash=sha256:6a9265cf87a5b70147bfb2fec14770ed5b11a5bb83353f0eee1c25a81af5abfe \ - --hash=sha256:6b1a950ab299a4a78fd6369a97b8763732bfb154fdb433356ec55a5bce9515c1 \ - --hash=sha256:6bb91fa4d0b392f0f7e27c40981e46dda9eb0fbc84162c7fb478fe115944f491 \ - --hash=sha256:6c21a5467f35c61eafb4e394303720893066897fca937bade5b4f5877d350ff8 \ - --hash=sha256:7ca6b71dcc50d320c88fb2d88ecd63924934a8abc1673683a242a7ca7d39e781 \ - --hash=sha256:7cf12ac34c444362f3261fb3ff548f0037ddd4c5bb85f66c4be30d2936beb3c5 \ - --hash=sha256:7f7252f52a09f8fa5435dc82b6af79483118ce6bd51eb74e6269f05ee22a7b9f \ - --hash=sha256:85042ab91814fca99cec4678fc063fb46df4cbb57b4835a1cc2cb7a51e10250e \ - --hash=sha256:857af85d445b9ba9178db95658c219dbd77b71b8264e66836a6eba4fbf49c320 \ - --hash=sha256:87f889f6e58849ddb7c5d2cb19e2e074917ed1c6e3ceca50405775166492cca8 \ - --hash=sha256:8ada449e22198c31fb013ae7e9add887e8d2bd2335401abd3cbc55f8c5083647 \ - --hash=sha256:8e56fbcdd27fce061854ddec99e015dd779cae186eb36b14471fc9ae713b118c \ - --hash=sha256:8f48c917ffd36ff9a5212614c2d0d585fa8b064ca7e66206fb5c095015bc8207 \ - --hash=sha256:9338ade39ff24f8086bb005d16c29f8e9f19e55b18dcb04dfa26fcbc09da497b \ - --hash=sha256:9837edf328b2805346f91209b7e660f65fb0e9ca18b7459d075d58db082bf981 \ - --hash=sha256:9d183e3888ada88185ab17064079c0db8c17e32023f5c278d7bf8014713b1b5b \ - --hash=sha256:9f02a259fcbbb5fcfe7a0805b1097ead5ba7a043e318eef1db59f93067f0b49b \ - --hash=sha256:9f8e6bb5ac007d4a4027b25f09827ed78cbbd5b9700fd6c54429278dacce05d1 \ - --hash=sha256:9ff785af8bacdf0be863ec0c428e3288b817e82f3d0c1d652cd9c6d509020dd0 \ - --hash=sha256:a0b2c25040a3c0ce0e66c7779cc045fdfbbb8d59e5aabfe033000b42fe44b53e \ - --hash=sha256:a753993635eccf1ecb185dedcc69d220dab41804272f45e4aef0a67e790c3eb3 \ - --hash=sha256:a8323daae27ea290ba3350c70c836c0d2b0fb47897fa3b0ca6a5375b952b90d3 \ - --hash=sha256:a8f195338a5a7b50a058522b39517c50238358d9ad8284fd92943643144c0c03 \ - --hash=sha256:a96ac14e184aa86dc43b8a22bb53854760a58b2966c2b41580de938e9bf26ed0 \ - --hash=sha256:aafea64a3ae698695975251f4254df2225e2624185a69534e7fe70581066bc1b \ - --hash=sha256:aba037c1310dd108411d27b3d5815998ef0e83573e47d4219f45753c710f969f \ - --hash=sha256:b1f67312efa3902a8e8496bfa9824d3bec096ff83c4669ea555c6bdd213aa516 \ - --hash=sha256:b4ac73b02ca1824ec0a7351588241fd3953748d3774694aa7ddb5e8e46aef3e3 \ - --hash=sha256:b8d3c5cd327dd6ce0edfc94374fb5883d254fe78a5e9d9dfc237a1897dc73cd1 \ - --hash=sha256:b98732ec893975455708d6fc9a6daab527fc8bbe65be354a3861f8c450a632a4 \ - --hash=sha256:ba31c32f6b4dceeb2be04f717811565159617e28d61a60bb616b6442027fd4b9 \ - --hash=sha256:bd3e2d64500a6cad28bcd710ee6269fbeb2e5320525acd0cfab5f269ade68581 \ - --hash=sha256:bee8ce357a05c20db04f46c22be2d1a2c6a8ed365b325d08af94358e0688eeb4 \ - --hash=sha256:c5e7803a65eb2d563c73230e9d693c6539e3c975ccfe62526cadde69f3fda0cf \ - --hash=sha256:c846884b2e690ba62a51048a097acb6b5cd263d8bd91062cd6137e2880578472 \ - --hash=sha256:d1aa4cc85202956d1a65c88d18c7b687b8319dbe6b1aec8969784ef7a10e7d1a \ - --hash=sha256:d2d42254b189a346249424fb9bb39182a19289a2409051ee432fb2926bad966a \ - --hash=sha256:dccc858372a56080332ea89b78cfb18efb945da858fabeb67f5a44fa0bcb4ebb \ - --hash=sha256:dd41d5c72417b87c00b1b635738f3c283e737d75c5fa5c3e1c60cd03eac3af77 \ - --hash=sha256:e087e8fdf1270d000913c12e6eca44edd02aad3559b3e6b8ef00f0ce76e0636f \ - --hash=sha256:e397b64f7aaf26915bf2ad0f1190f75c855d11eb111cc00f12f97430153c2eab \ - --hash=sha256:e495ed2a7943503766c5d1ff05ae9212dc2ce1c0e30a80d4f0d84889298fa304 \ - --hash=sha256:e75695cc952e825fa3e0684a7f4a302f9128721f13eedd8dbd3af2ba450932b8 \ - --hash=sha256:eb99c954291b2fad0eff98b490aa641e128fbc4a03b11c8a0086de8b7077fb75 \ - --hash=sha256:ecf2be4b9eece4f3da8ba5f244b9e51932ebc441c0867bd6af46a3d97eb068d6 \ - --hash=sha256:ee1f5fcbf5bc33acc0be9dd31130bcba35d6d2302e4eceafafd7d9018c7755ab \ - --hash=sha256:ee7db6e36e7a2c15923072e41ea24d9a0cf39658cb0637ecc9307b09d28827e1 \ - --hash=sha256:efadd40fca3a04063d40c4448c9303ce24dd6151dc162cfae4a2a060232ebdcb \ - --hash=sha256:f18de0f82c62c4197bea5ecf4389288ac755896aac734bd2cc44004c56e4ac47 \ - --hash=sha256:f449afbb971df5c6faeb0a27bca0427d7b600dd8f4a068492faec18023f0dcff \ - --hash=sha256:f46c6f0aec8d02a52d97a583782d9af38c19a29900747eb048af358a9c1d8e5b \ - --hash=sha256:fb02d41c33be667e6135e6686f1bb76104c88a312a18faa0ef0262b5bf7f1a0f \ - --hash=sha256:fd257f98cff9c6cb39eee1a83c7c3183970d8a8d23e8cf4f47d9a21329285cee +watchfiles==0.24.0 # via uvicorn -websockets==13.0 \ - --hash=sha256:02cc9bb1a887dac0e08bf657c5d00aa3fac0d03215d35a599130c2034ae6663a \ - --hash=sha256:038e7a0f1bfafc7bf52915ab3506b7a03d1e06381e9f60440c856e8918138151 \ - --hash=sha256:05c25f7b849702950b6fd0e233989bb73a0d2bc83faa3b7233313ca395205f6d \ - --hash=sha256:06b3186e97bf9a33921fa60734d5ed90f2a9b407cce8d23c7333a0984049ef61 \ - --hash=sha256:06df8306c241c235075d2ae77367038e701e53bc8c1bb4f6644f4f53aa6dedd0 \ - --hash=sha256:0a8f7d65358a25172db00c69bcc7df834155ee24229f560d035758fd6613111a \ - --hash=sha256:1f661a4205741bdc88ac9c2b2ec003c72cee97e4acd156eb733662ff004ba429 \ - --hash=sha256:265e1f0d3f788ce8ef99dca591a1aec5263b26083ca0934467ad9a1d1181067c \ - --hash=sha256:2be1382a4daa61e2f3e2be3b3c86932a8db9d1f85297feb6e9df22f391f94452 \ - --hash=sha256:2e1cf4e1eb84b4fd74a47688e8b0940c89a04ad9f6937afa43d468e71128cd68 \ - --hash=sha256:337837ac788d955728b1ab01876d72b73da59819a3388e1c5e8e05c3999f1afa \ - --hash=sha256:358d37c5c431dd050ffb06b4b075505aae3f4f795d7fff9794e5ed96ce99b998 \ - --hash=sha256:35c2221b539b360203f3f9ad168e527bf16d903e385068ae842c186efb13d0ea \ - --hash=sha256:3670def5d3dfd5af6f6e2b3b243ea8f1f72d8da1ef927322f0703f85c90d9603 \ - --hash=sha256:372f46a0096cfda23c88f7e42349a33f8375e10912f712e6b496d3a9a557290f \ - --hash=sha256:376a43a4fd96725f13450d3d2e98f4f36c3525c562ab53d9a98dd2950dca9a8a \ - --hash=sha256:384129ad0490e06bab2b98c1da9b488acb35bb11e2464c728376c6f55f0d45f3 \ - --hash=sha256:3a20cf14ba7b482c4a1924b5e061729afb89c890ca9ed44ac4127c6c5986e424 \ - --hash=sha256:3e6566e79c8c7cbea75ec450f6e1828945fc5c9a4769ceb1c7b6e22470539712 \ - --hash=sha256:4782ec789f059f888c1e8fdf94383d0e64b531cffebbf26dd55afd53ab487ca4 \ - --hash=sha256:4d70c89e3d3b347a7c4d3c33f8d323f0584c9ceb69b82c2ef8a174ca84ea3d4a \ - --hash=sha256:516062a0a8ef5ecbfa4acbaec14b199fc070577834f9fe3d40800a99f92523ca \ - --hash=sha256:5575031472ca87302aeb2ce2c2349f4c6ea978c86a9d1289bc5d16058ad4c10a \ - --hash=sha256:587245f0704d0bb675f919898d7473e8827a6d578e5a122a21756ca44b811ec8 \ - --hash=sha256:602cbd010d8c21c8475f1798b705bb18567eb189c533ab5ef568bc3033fdf417 \ - --hash=sha256:6058b6be92743358885ad6dcdecb378fde4a4c74d4dd16a089d07580c75a0e80 \ - --hash=sha256:63b702fb31e3f058f946ccdfa551f4d57a06f7729c369e8815eb18643099db37 \ - --hash=sha256:6ad684cb7efce227d756bae3e8484f2e56aa128398753b54245efdfbd1108f2c \ - --hash=sha256:6fd757f313c13c34dae9f126d3ba4cf97175859c719e57c6a614b781c86b617e \ - --hash=sha256:7334752052532c156d28b8eaf3558137e115c7871ea82adff69b6d94a7bee273 \ - --hash=sha256:788bc841d250beccff67a20a5a53a15657a60111ef9c0c0a97fbdd614fae0fe2 \ - --hash=sha256:7d14901fdcf212804970c30ab9ee8f3f0212e620c7ea93079d6534863444fb4e \ - --hash=sha256:7ea9c9c7443a97ea4d84d3e4d42d0e8c4235834edae652993abcd2aff94affd7 \ - --hash=sha256:81a11a1ddd5320429db47c04d35119c3e674d215173d87aaeb06ae80f6e9031f \ - --hash=sha256:851fd0afb3bc0b73f7c5b5858975d42769a5fdde5314f4ef2c106aec63100687 \ - --hash=sha256:85a1f92a02f0b8c1bf02699731a70a8a74402bb3f82bee36e7768b19a8ed9709 \ - --hash=sha256:89d795c1802d99a643bf689b277e8604c14b5af1bc0a31dade2cd7a678087212 \ - --hash=sha256:9202c0010c78fad1041e1c5285232b6508d3633f92825687549540a70e9e5901 \ - --hash=sha256:939a16849d71203628157a5e4a495da63967c744e1e32018e9b9e2689aca64d4 \ - --hash=sha256:93b8c2008f372379fb6e5d2b3f7c9ec32f7b80316543fd3a5ace6610c5cde1b0 \ - --hash=sha256:94c1c02721139fe9940b38d28fb15b4b782981d800d5f40f9966264fbf23dcc8 \ - --hash=sha256:9895df6cd0bfe79d09bcd1dbdc03862846f26fbd93797153de954306620c1d00 \ - --hash=sha256:9cc7f35dcb49a4e32db82a849fcc0714c4d4acc9d2273aded2d61f87d7f660b7 \ - --hash=sha256:9ed02c604349068d46d87ef4c2012c112c791f2bec08671903a6bb2bd9c06784 \ - --hash=sha256:a00e1e587c655749afb5b135d8d3edcfe84ec6db864201e40a882e64168610b3 \ - --hash=sha256:a1ab8f0e0cadc5be5f3f9fa11a663957fecbf483d434762c8dfb8aa44948944a \ - --hash=sha256:a4de299c947a54fca9ce1c5fd4a08eb92ffce91961becb13bd9195f7c6e71b47 \ - --hash=sha256:a7fbf2a8fe7556a8f4e68cb3e736884af7bf93653e79f6219f17ebb75e97d8f0 \ - --hash=sha256:ad4fa707ff9e2ffee019e946257b5300a45137a58f41fbd9a4db8e684ab61528 \ - --hash=sha256:ad818cdac37c0ad4c58e51cb4964eae4f18b43c4a83cb37170b0d90c31bd80cf \ - --hash=sha256:addf0a16e4983280efed272d8cb3b2e05f0051755372461e7d966b80a6554e16 \ - --hash=sha256:ae7a519a56a714f64c3445cabde9fc2fc927e7eae44f413eae187cddd9e54178 \ - --hash=sha256:b32f38bc81170fd56d0482d505b556e52bf9078b36819a8ba52624bd6667e39e \ - --hash=sha256:b5407c34776b9b77bd89a5f95eb0a34aaf91889e3f911c63f13035220eb50107 \ - --hash=sha256:b7bf950234a482b7461afdb2ec99eee3548ec4d53f418c7990bb79c620476602 \ - --hash=sha256:b89849171b590107f6724a7b0790736daead40926ddf47eadf998b4ff51d6414 \ - --hash=sha256:bcea3eb58c09c3a31cc83b45c06d5907f02ddaf10920aaa6443975310f699b95 \ - --hash=sha256:bd4ba86513430513e2aa25a441bb538f6f83734dc368a2c5d18afdd39097aa33 \ - --hash=sha256:bf8eb5dca4f484a60f5327b044e842e0d7f7cdbf02ea6dc4a4f811259f1f1f0b \ - --hash=sha256:c026ee729c4ce55708a14b839ba35086dfae265fc12813b62d34ce33f4980c1c \ - --hash=sha256:c210d1460dc8d326ffdef9703c2f83269b7539a1690ad11ae04162bc1878d33d \ - --hash=sha256:c8feb8e19ef65c9994e652c5b0324abd657bedd0abeb946fb4f5163012c1e730 \ - --hash=sha256:cbac2eb7ce0fac755fb983c9247c4a60c4019bcde4c0e4d167aeb17520cc7ef1 \ - --hash=sha256:cbfe82a07596a044de78bb7a62519e71690c5812c26c5f1d4b877e64e4f46309 \ - --hash=sha256:d3f3d2e20c442b58dbac593cb1e02bc02d149a86056cc4126d977ad902472e3b \ - --hash=sha256:d42a818e634f789350cd8fb413a3f5eec1cf0400a53d02062534c41519f5125c \ - --hash=sha256:d4b83cf7354cbbc058e97b3e545dceb75b8d9cf17fd5a19db419c319ddbaaf7a \ - --hash=sha256:d9726d2c9bd6aed8cb994d89b3910ca0079406edce3670886ec828a73e7bdd53 \ - --hash=sha256:da7e501e59857e8e3e9d10586139dc196b80445a591451ca9998aafba1af5278 \ - --hash=sha256:da7e918d82e7bdfc6f66d31febe1b2e28a1ca3387315f918de26f5e367f61572 \ - --hash=sha256:dbbac01e80aee253d44c4f098ab3cc17c822518519e869b284cfbb8cd16cc9de \ - --hash=sha256:df5c0eff91f61b8205a6c9f7b255ff390cdb77b61c7b41f79ca10afcbb22b6cb \ - --hash=sha256:e07e76c49f39c5b45cbd7362b94f001ae209a3ea4905ae9a09cfd53b3c76373d \ - --hash=sha256:e1e10b3fbed7be4a59831d3a939900e50fcd34d93716e433d4193a4d0d1d335d \ - --hash=sha256:e39d393e0ab5b8bd01717cc26f2922026050188947ff54fe6a49dc489f7750b7 \ - --hash=sha256:e5ba5e9b332267d0f2c33ede390061850f1ac3ee6cd1bdcf4c5ea33ead971966 \ - --hash=sha256:e7a1963302947332c3039e3f66209ec73b1626f8a0191649e0713c391e9f5b0d \ - --hash=sha256:e7fcad070dcd9ad37a09d89a4cbc2a5e3e45080b88977c0da87b3090f9f55ead \ - --hash=sha256:eae368cac85adc4c7dc3b0d5f84ffcca609d658db6447387300478e44db70796 \ - --hash=sha256:ede95125a30602b1691a4b1da88946bf27dae283cf30f22cd2cb8ca4b2e0d119 \ - --hash=sha256:f5737c53eb2c8ed8f64b50d3dafd3c1dae739f78aa495a288421ac1b3de82717 \ - --hash=sha256:f5f9d23fbbf96eefde836d9692670bfc89e2d159f456d499c5efcf6a6281c1af \ - --hash=sha256:f66e00e42f25ca7e91076366303e11c82572ca87cc5aae51e6e9c094f315ab41 \ - --hash=sha256:f9af457ed593e35f467140d8b61d425495b127744a9d65d45a366f8678449a23 \ - --hash=sha256:fa0839f35322f7b038d8adcf679e2698c3a483688cc92e3bd15ee4fb06669e9a \ - --hash=sha256:fd038bc9e2c134847f1e0ce3191797fad110756e690c2fdd9702ed34e7a43abb +websockets==13.1 # via # -r requirements/main.in + # rubin-nublado-client # uvicorn diff --git a/src/mobu/__init__.py b/src/mobu/__init__.py index 48a99328..418755b0 100644 --- a/src/mobu/__init__.py +++ b/src/mobu/__init__.py @@ -1,6 +1,6 @@ """The mobu service.""" -__all__ = ["__version__", "metadata"] +__all__ = ["__version__", "metadata", "main"] from importlib.metadata import PackageNotFoundError, version diff --git a/src/mobu/exceptions.py b/src/mobu/exceptions.py index a3724b04..35f49a49 100644 --- a/src/mobu/exceptions.py +++ b/src/mobu/exceptions.py @@ -2,28 +2,46 @@ from __future__ import annotations +import datetime import json import re -from datetime import datetime from pathlib import Path from typing import Self from fastapi import status from pydantic import ValidationError -from safir.datetime import format_datetime_for_logging +from rubin.nublado.client.exceptions import ( + CodeExecutionError as ClientCodeExecutionError, +) +from rubin.nublado.client.exceptions import ( + JupyterProtocolError as ClientJupyterProtocolError, +) +from rubin.nublado.client.exceptions import ( + JupyterSpawnError as ClientJupyterSpawnError, +) +from rubin.nublado.client.exceptions import ( + JupyterTimeoutError as ClientJupyterTimeoutError, +) +from rubin.nublado.client.exceptions import ( + JupyterWebError as ClientJupyterWebError, +) +from rubin.nublado.client.exceptions import ( + JupyterWebSocketError as ClientJupyterWebSocketError, +) +from rubin.nublado.client.exceptions import ( + NubladoClientSlackException, + NubladoClientSlackWebException, +) from safir.fastapi import ClientRequestError from safir.models import ErrorLocation from safir.slack.blockkit import ( SlackBaseBlock, SlackBaseField, SlackCodeBlock, - SlackException, SlackMessage, SlackTextBlock, SlackTextField, - SlackWebException, ) -from websockets.exceptions import InvalidStatus, WebSocketException _ANSI_REGEX = re.compile(r"(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]") """Regex that matches ANSI escape sequences.""" @@ -38,6 +56,7 @@ "JupyterProtocolError", "JupyterTimeoutError", "JupyterWebError", + "MobuMixin", "MobuSlackException", "MobuSlackWebException", "MonkeyNotFoundError", @@ -69,7 +88,31 @@ def _remove_ansi_escapes(string: str) -> str: return _ANSI_REGEX.sub("", string) -class GafaelfawrParseError(SlackException): +class MobuMixin: + """Mixin class to add `event` and `monkey` fields to Exception.""" + + def __init__( + self, event: str | None = None, monkey: str | None = None + ) -> None: + self.mobu_init() + + def mobu_init( + self, event: str | None = None, monkey: str | None = None + ) -> None: + """Initialize mobu-specific fields.""" + self.event: str | None = event + self.monkey: str | None = monkey + + def mobu_fields(self) -> list[SlackBaseField]: + fields: list[SlackBaseField] = [] + if self.event: + fields.append(SlackTextField(heading="Event", text=self.event)) + if self.monkey: + fields.append(SlackTextField(heading="Monkey", text=self.monkey)) + return fields + + +class GafaelfawrParseError(NubladoClientSlackException): """Unable to parse the reply from Gafaelfawr. Parameters @@ -123,7 +166,7 @@ def to_slack(self) -> SlackMessage: return message -class GafaelfawrWebError(SlackWebException): +class GafaelfawrWebError(NubladoClientSlackWebException): """An API call to Gafaelfawr failed.""" @@ -151,7 +194,7 @@ def __init__(self, monkey: str) -> None: super().__init__(msg, ErrorLocation.path, ["monkey"]) -class MobuSlackException(SlackException): +class MobuSlackException(NubladoClientSlackException, MobuMixin): """Represents an exception that can be reported to Slack. This adds some additional fields to `~safir.slack.blockkit.SlackException` @@ -186,14 +229,34 @@ def __init__( msg: str, user: str | None = None, *, - started_at: datetime | None = None, - failed_at: datetime | None = None, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, + monkey: str | None = None, + event: str | None = None, ) -> None: - super().__init__(msg, user, failed_at=failed_at) - self.started_at = started_at - self.monkey: str | None = None - self.event: str | None = None - self.annotations: dict[str, str] = {} + super().__init__(msg, user, failed_at=failed_at, started_at=started_at) + self.mobu_init(monkey=monkey, event=event) + + @classmethod + def from_slack_exception(cls, exc: NubladoClientSlackException) -> Self: + return cls( + msg=exc.message, + user=exc.user, + started_at=exc.started_at, + failed_at=exc.failed_at, + ) + + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. + + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + return fields def to_slack(self) -> SlackMessage: """Format the error as a Slack Block Kit message. @@ -212,70 +275,25 @@ def to_slack(self) -> SlackMessage: fields=self.common_fields(), ) - def common_blocks(self) -> list[SlackBaseBlock]: - """Return common blocks to put in any alert. - - Returns - ------- - list of SlackBaseBlock - Common blocks to add to the Slack message. - """ - blocks: list[SlackBaseBlock] = [] - if self.annotations.get("node"): - node = self.annotations["node"] - blocks.append(SlackTextBlock(heading="Node", text=node)) - if self.annotations.get("notebook"): - notebook = self.annotations["notebook"] - if self.annotations.get("cell"): - cell = self.annotations["cell"] - text = f"`{notebook}` cell {cell}" - blocks.append(SlackTextBlock(heading="Cell", text=text)) - else: - block = SlackTextBlock(heading="Notebook", text=notebook) - blocks.append(block) - elif self.annotations.get("cell"): - cell = self.annotations["cell"] - blocks.append(SlackTextBlock(heading="Cell", text=cell)) - return blocks - - def common_fields(self) -> list[SlackBaseField]: - """Return common fields to put in any alert. - - Returns - ------- - list of SlackBaseField - Common fields to add to the Slack message. - """ - failed_at = format_datetime_for_logging(self.failed_at) - fields: list[SlackBaseField] = [ - SlackTextField(heading="Failed at", text=failed_at), - SlackTextField(heading="Exception type", text=type(self).__name__), - ] - if self.started_at: - started_at = format_datetime_for_logging(self.started_at) - field = SlackTextField(heading="Started at", text=started_at) - fields.insert(0, field) - if self.monkey: - fields.append(SlackTextField(heading="Monkey", text=self.monkey)) - if self.user: - fields.append(SlackTextField(heading="User", text=self.user)) - if self.event: - fields.append(SlackTextField(heading="Event", text=self.event)) - if self.annotations.get("image"): - image = self.annotations["image"] - fields.append(SlackTextField(heading="Image", text=image)) - return fields - -class MobuSlackWebException(SlackWebException, MobuSlackException): +class MobuSlackWebException( + NubladoClientSlackWebException, MobuSlackException +): """Represents an exception that can be reported to Slack. Similar to `MobuSlackException`, this adds some additional fields to - `~safir.slack.blockkit.SlackWebException` but is otherwise equivalent. It + `~rubin.nublado.client.SlackWebException` but is otherwise equivalent. It is intended to be subclassed. Subclasses may want to override the `to_slack` method. """ + def common_blocks(self) -> list[SlackBaseBlock]: + blocks = MobuSlackException.common_blocks(self) + if self.url: + text = f"{self.method} {self.url}" if self.method else self.url + blocks.append(SlackTextBlock(heading="URL", text=text)) + return blocks + class NotebookRepositoryError(MobuSlackException): """The repository containing notebooks to run is not valid.""" @@ -316,7 +334,7 @@ def to_slack(self) -> SlackMessage: return message -class CodeExecutionError(MobuSlackException): +class CodeExecutionError(ClientCodeExecutionError, MobuMixin): """Error generated by code execution in a notebook on JupyterLab.""" def __init__( @@ -327,12 +345,19 @@ def __init__( code_type: str = "code", error: str | None = None, status: str | None = None, + monkey: str | None = None, + event: str | None = None, + started_at: datetime.datetime | None = None, ) -> None: - super().__init__("Code execution failed", user) - self.code = code - self.code_type = code_type - self.error = error - self.status = status + super().__init__( + user=user, + code=code, + code_type=code_type, + error=error, + status=status, + started_at=started_at, + ) + self.mobu_init(monkey=monkey, event=event) def __str__(self) -> str: if self.annotations.get("notebook"): @@ -351,36 +376,70 @@ def __str__(self) -> str: else: msg = f"{self.user}: running {self.code_type} failed" if self.error: - msg += f"\nError: {_remove_ansi_escapes(self.error)}" + if self.error: + msg += f"\nError: {_remove_ansi_escapes(self.error)}" return msg - def to_slack(self) -> SlackMessage: - """Format the error as a Slack Block Kit message.""" - if self.annotations.get("notebook"): - notebook = self.annotations["notebook"] - intro = f"Error while running `{notebook}`" - else: - intro = f"Error while running {self.code_type}" - if self.status: - intro += f" (status: {self.status})" + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. - attachments: list[SlackBaseBlock] = [] - if self.error: - error = _remove_ansi_escapes(self.error) - attachment = SlackCodeBlock(heading="Error", code=error) - attachments.append(attachment) - if self.code: - attachment = SlackCodeBlock( - heading="Code executed", code=self.code - ) - attachments.append(attachment) + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + return fields - return SlackMessage( - message=intro, - fields=self.common_fields(), - blocks=self.common_blocks(), - attachments=attachments, + @classmethod + def from_client_exception( + cls, + exc: ClientCodeExecutionError, + monkey: str | None = None, + event: str | None = None, + annotations: dict[str, str] | None = None, + started_at: datetime.datetime | None = None, + ) -> Self: + """ + Add Mobu-specific fields to exception from NubladoClient layer. + + Parameters + ---------- + exc + Original exception + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. + + Returns + ------- + CodeExecutionError + Converted exception. + """ + anno: dict[str, str] = {} + if annotations is not None: + anno.update(annotations) + new_exc = cls( + user=exc.user or "", + code=exc.code, + code_type=exc.code_type, + error=exc.error, + status=exc.status, + started_at=started_at or exc.started_at, + monkey=monkey, + event=event, ) + if exc.annotations is not None: + new_exc.annotations.update(exc.annotations) + new_exc.annotations.update(anno) + return new_exc class GitHubFileNotFoundError(Exception): @@ -389,15 +448,168 @@ class GitHubFileNotFoundError(Exception): """ -class JupyterProtocolError(MobuSlackException): +class JupyterProtocolError(ClientJupyterProtocolError, MobuMixin): """Some error occurred when talking to JupyterHub or JupyterLab.""" + def __init__( + self, + msg: str, + user: str | None = None, + *, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, + monkey: str | None = None, + event: str | None = None, + ) -> None: + super().__init__( + msg=msg, user=user, started_at=started_at, failed_at=failed_at + ) + self.mobu_init(monkey=monkey, event=event) + + @classmethod + def from_client_exception( + cls, + exc: ClientJupyterProtocolError, + event: str | None = None, + monkey: str | None = None, + started_at: datetime.datetime | None = None, + annotations: dict[str, str] | None = None, + ) -> Self: + """ + Add Mobu-specific fields to exception from NubladoClient layer. + + Parameters + ---------- + exc + Original exception + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. -class JupyterSpawnError(MobuSlackException): + Returns + ------- + JupyterProtocolError + Converted exception. + """ + anno: dict[str, str] = {} + if annotations is not None: + anno = annotations + new_exc = cls( + msg=exc.message, + user=exc.user, + started_at=started_at or exc.started_at, + failed_at=exc.failed_at, + monkey=monkey, + event=event, + ) + if exc.annotations is not None: + new_exc.annotations.update(exc.annotations) + new_exc.annotations.update(anno) + return new_exc + + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. + + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + return fields + + +class JupyterSpawnError(ClientJupyterSpawnError, MobuMixin): """The Jupyter Lab pod failed to spawn.""" + def __init__( + self, + log: str, + user: str, + message: str | None = None, + monkey: str | None = None, + event: str | None = None, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, + ) -> None: + if message: + message = f"Spawning lab failed: {message}" + else: + message = "Spawning lab failed" + super().__init__( + message, user, started_at=started_at, failed_at=failed_at + ) + self.log = log + self.mobu_init(monkey=monkey, event=event) + + @classmethod + def from_client_exception( + cls, + exc: ClientJupyterSpawnError, + monkey: str | None = None, + event: str | None = None, + annotations: dict[str, str] | None = None, + started_at: datetime.datetime | None = None, + ) -> Self: + """ + Add Mobu-specific fields to exception from NubladoClient layer. + + Parameters + ---------- + exc + Original exception + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. + + Returns + ------- + JupyterSpawnError + Converted exception. + """ + anno: dict[str, str] = {} + if annotations is not None: + anno = annotations + new_exc = cls( + log=exc.log, + user=exc.user or "", + message=exc.message, + monkey=monkey, + event=event, + started_at=started_at or exc.started_at, + failed_at=exc.failed_at, + ) + if exc.annotations is not None: + new_exc.annotations.update(exc.annotations) + new_exc.annotations.update(anno) + return new_exc + @classmethod - def from_exception(cls, log: str, exc: Exception, user: str) -> Self: + def from_exception( + cls, + log: str, + exc: Exception, + user: str, + *, + monkey: str | None = None, + event: str | None = None, + annotations: dict[str, str] | None = None, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, + ) -> Self: """Convert from an arbitrary exception to a spawn error. Parameters @@ -408,37 +620,50 @@ def from_exception(cls, log: str, exc: Exception, user: str) -> Self: Exception that terminated the spawn attempt. user Username of the user spawning the lab. + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. Returns ------- JupyterSpawnError Converted exception. """ - if str(exc): - return cls(log, user, f"{type(exc).__name__}: {exc!s}") - else: - return cls(log, user, type(exc).__name__) + anno: dict[str, str] = {} + if annotations is not None: + anno = annotations + client_exc = super().from_exception(log, exc, user) + new_exc = cls.from_client_exception( + client_exc, + monkey=monkey, + event=event, + started_at=started_at or client_exc.started_at, + ) + if client_exc.annotations is not None: + new_exc.annotations.update(client_exc.annotations) + new_exc.annotations.update(anno) + return new_exc - def __init__( - self, log: str, user: str, message: str | None = None - ) -> None: - if message: - message = f"Spawning lab failed: {message}" - else: - message = "Spawning lab failed" - super().__init__(message, user) - self.log = log + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. - def to_slack(self) -> SlackMessage: - """Format the error as a Slack Block Kit message.""" - message = super().to_slack() - if self.log: - block = SlackTextBlock(heading="Log", text=self.log) - message.blocks.append(block) - return message + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + return fields -class JupyterTimeoutError(MobuSlackException): +class JupyterTimeoutError(ClientJupyterTimeoutError, MobuMixin): """Timed out waiting for the lab to spawn.""" def __init__( @@ -447,56 +672,165 @@ def __init__( user: str, log: str | None = None, *, - started_at: datetime | None = None, + monkey: str | None = None, + event: str | None = None, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, ) -> None: super().__init__(msg, user, started_at=started_at) self.log = log + self.mobu_init(monkey=monkey, event=event) - def to_slack(self) -> SlackMessage: - """Format the error as a Slack Block Kit message.""" - message = super().to_slack() - if self.log: - message.blocks.append(SlackTextBlock(heading="Log", text=self.log)) - return message + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + image = self.annotations.get("image") + if image: + fields.append(SlackTextField(heading="Image", text=image)) + return fields -class JupyterWebError(MobuSlackWebException): - """An error occurred when talking to JupyterHub or a Jupyter lab.""" + @classmethod + def from_client_exception( + cls, + exc: ClientJupyterTimeoutError, + monkey: str | None = None, + event: str | None = None, + annotations: dict[str, str] | None = None, + started_at: datetime.datetime | None = None, + ) -> Self: + """ + Add Mobu-specific fields to exception from NubladoClient layer. + Parameters + ---------- + exc + Original exception + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. + + Returns + ------- + JupyterTimeoutError + Converted exception. + """ + anno: dict[str, str] = {} + if annotations is not None: + anno = annotations + new_exc = cls( + log=exc.log, + user=exc.user or "", + msg=exc.message, + monkey=monkey, + event=event, + started_at=started_at or exc.started_at, + failed_at=exc.failed_at, + ) + if exc.annotations is not None: + new_exc.annotations.update(exc.annotations) + new_exc.annotations.update(anno) + return new_exc -class JupyterWebSocketError(MobuSlackException): - """An error occurred talking to the Jupyter lab WebSocket.""" + +class JupyterWebError(ClientJupyterWebError, MobuMixin): + """An error occurred when talking to JupyterHub or a Jupyter lab.""" + + def __init__( + self, + msg: str, + user: str | None = None, + *, + monkey: str | None = None, + event: str | None = None, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, + ) -> None: + super().__init__(message=msg, user=user, failed_at=failed_at) + self.started_at = started_at + self.mobu_init(monkey=monkey, event=event) @classmethod - def from_exception(cls, exc: WebSocketException, user: str) -> Self: - """Convert from a `~websockets.exceptions.WebSocketException`. + def from_client_exception( + cls, + exc: ClientJupyterWebError, + monkey: str | None = None, + event: str | None = None, + annotations: dict[str, str] | None = None, + started_at: datetime.datetime | None = None, + ) -> Self: + """ + Add Mobu-specific fields to exception from NubladoClient layer. Parameters ---------- exc - Underlying exception. - user - User the monkey is running as. + Original exception + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. Returns ------- - JupyterWebSocketError - Newly-created exception. + JupyterWebError + Converted exception. """ - if str(exc): - error = f"{type(exc).__name__}: {exc!s}" - else: - error = type(exc).__name__ - if isinstance(exc, InvalidStatus): - status = exc.response.status_code - return cls( - f"Lab WebSocket unexpectedly closed: {error}", - user=user, - status=status, - body=exc.response.body, - ) - else: - return cls(f"Error talking to lab WebSocket: {error}", user=user) + anno: dict[str, str] = {} + if annotations is not None: + anno = annotations + new_exc = cls( + msg=exc.message, + user=exc.user, + started_at=started_at or exc.started_at, + failed_at=exc.failed_at, + monkey=monkey, + event=event, + ) + if exc.annotations is not None: + new_exc.annotations.update(exc.annotations) + new_exc.annotations.update(anno) + new_exc.event = event + if exc.method is not None: + new_exc.method = exc.method + if exc.url is not None: + new_exc.url = exc.url + if exc.body is not None: + new_exc.body = exc.body + return new_exc + + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. + + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + return fields + + +class JupyterWebSocketError(ClientJupyterWebSocketError, MobuMixin): + """An error occurred talking to the Jupyter lab WebSocket.""" def __init__( self, @@ -507,12 +841,21 @@ def __init__( reason: str | None = None, status: int | None = None, body: bytes | None = None, + monkey: str | None = None, + event: str | None = None, + started_at: datetime.datetime | None = None, + failed_at: datetime.datetime | None = None, ) -> None: - super().__init__(msg, user) - self.code = code - self.reason = reason - self.status = status - self.body = body.decode() if body else None + super().__init__( + msg=msg, + user=user, + code=code, + reason=reason, + status=status, + started_at=started_at, + body=body, + ) + self.mobu_init(monkey=monkey, event=event) def to_slack(self) -> SlackMessage: """Format this exception as a Slack notification. @@ -542,6 +885,72 @@ def to_slack(self) -> SlackMessage: return message + def common_fields(self) -> list[SlackBaseField]: + """Return common fields to put in any alert. + + Returns + ------- + list of SlackBaseField + Common fields to add to the Slack message. + """ + fields = super().common_fields() + fields.extend(self.mobu_fields()) + return fields + + @classmethod + def from_client_exception( + cls, + exc: ClientJupyterWebSocketError, + monkey: str | None = None, + event: str | None = None, + annotations: dict[str, str] | None = None, + started_at: datetime.datetime | None = None, + ) -> Self: + """ + Add Mobu-specific fields to exception from NubladoClient layer. + + Parameters + ---------- + exc + Original exception + monkey + Monkey spawning the lab, if known. + event + Event (from mobu's perspective) spawning the lab, if known. + annotations + Additional annotations + started_at + Timestamp for beginning of operation that caused the exception, + if known. + + Returns + ------- + JupyterWebSocketError + Converted exception. + """ + anno: dict[str, str] = {} + if annotations is not None: + anno = annotations + body = exc.body + if body is not None: + body_bytes = body.encode() + new_exc = cls( + msg=exc.message, + user=exc.user or "", + code=exc.code, + reason=exc.reason, + status=exc.status, + body=body_bytes, + monkey=monkey, + event=event, + started_at=started_at or exc.started_at, + failed_at=exc.failed_at, + ) + if exc.annotations is not None: + new_exc.annotations.update(exc.annotations) + new_exc.annotations.update(anno) + return new_exc + class TAPClientError(MobuSlackException): """Creating a TAP client failed.""" diff --git a/src/mobu/models/business/nublado.py b/src/mobu/models/business/nublado.py index a85a9db1..8cd30fa1 100644 --- a/src/mobu/models/business/nublado.py +++ b/src/mobu/models/business/nublado.py @@ -2,143 +2,24 @@ from __future__ import annotations -from abc import ABCMeta, abstractmethod from datetime import timedelta -from enum import Enum -from typing import Literal from pydantic import BaseModel, Field +from rubin.nublado.client.models import ( + NubladoImageByClass, + NubladoImageByReference, + NubladoImageByTag, +) from .base import BusinessData, BusinessOptions, SerializableTimedelta __all__ = [ "NubladoBusinessData", "NubladoBusinessOptions", - "NubladoImage", - "NubladoImageByClass", - "NubladoImageByReference", - "NubladoImageByTag", - "NubladoImageClass", - "NubladoImageSize", "RunningImage", ] -class NubladoImageClass(str, Enum): - """Possible ways of selecting an image.""" - - __slots__ = () - - RECOMMENDED = "recommended" - LATEST_RELEASE = "latest-release" - LATEST_WEEKLY = "latest-weekly" - LATEST_DAILY = "latest-daily" - BY_REFERENCE = "by-reference" - BY_TAG = "by-tag" - - -class NubladoImageSize(Enum): - """Acceptable sizes of images to spawn.""" - - Fine = "Fine" - Diminutive = "Diminutive" - Tiny = "Tiny" - Small = "Small" - Medium = "Medium" - Large = "Large" - Huge = "Huge" - Gargantuan = "Gargantuan" - Colossal = "Colossal" - - -class NubladoImage(BaseModel, metaclass=ABCMeta): - """Base class for different ways of specifying the lab image to spawn.""" - - # Ideally this would just be class, but it is a keyword and adding all the - # plumbing to correctly serialize Pydantic models by alias instead of - # field name is tedious and annoying. Live with the somewhat verbose name. - image_class: NubladoImageClass = Field( - ..., - title="Class of image to spawn", - ) - - size: NubladoImageSize = Field( - NubladoImageSize.Large, - title="Size of image to spawn", - description="Must be one of the sizes understood by Nublado.", - ) - - debug: bool = Field(False, title="Whether to enable lab debugging") - - @abstractmethod - def to_spawn_form(self) -> dict[str, str]: - """Convert to data suitable for posting to Nublado's spawn form. - - Returns - ------- - dict of str - Post data to send to the JupyterHub spawn page. - """ - - -class NubladoImageByClass(NubladoImage): - """Spawn the recommended image.""" - - image_class: Literal[ - NubladoImageClass.RECOMMENDED, - NubladoImageClass.LATEST_RELEASE, - NubladoImageClass.LATEST_WEEKLY, - NubladoImageClass.LATEST_DAILY, - ] = Field( - NubladoImageClass.RECOMMENDED, - title="Class of image to spawn", - ) - - def to_spawn_form(self) -> dict[str, str]: - result = { - "image_class": self.image_class.value, - "size": self.size.value, - } - if self.debug: - result["enable_debug"] = "true" - return result - - -class NubladoImageByReference(NubladoImage): - """Spawn an image by full Docker reference.""" - - image_class: Literal[NubladoImageClass.BY_REFERENCE] = Field( - NubladoImageClass.BY_REFERENCE, title="Class of image to spawn" - ) - - reference: str = Field(..., title="Docker reference of lab image to spawn") - - def to_spawn_form(self) -> dict[str, str]: - result = { - "image_list": self.reference, - "size": self.size.value, - } - if self.debug: - result["enable_debug"] = "true" - return result - - -class NubladoImageByTag(NubladoImage): - """Spawn an image by image tag.""" - - image_class: Literal[NubladoImageClass.BY_TAG] = Field( - NubladoImageClass.BY_TAG, title="Class of image to spawn" - ) - - tag: str = Field(..., title="Tag of image to spawn") - - def to_spawn_form(self) -> dict[str, str]: - result = {"image_tag": self.tag, "size": self.size.value} - if self.debug: - result["enable_debug"] = "true" - return result - - class NubladoBusinessOptions(BusinessOptions): """Options for any business that runs code in a Nublado lab.""" diff --git a/src/mobu/models/user.py b/src/mobu/models/user.py index 26760583..6f1fc389 100644 --- a/src/mobu/models/user.py +++ b/src/mobu/models/user.py @@ -3,6 +3,7 @@ from __future__ import annotations from pydantic import BaseModel, Field +from rubin.nublado.client.models import User as ClientUser __all__ = [ "AuthenticatedUser", @@ -94,3 +95,6 @@ class AuthenticatedUser(User): title="Authentication token for user", examples=["gt-1PhgAeB-9Fsa-N1NhuTu_w.oRvMvAQp1bWfx8KCJKNohg"], ) + + def to_client_user(self) -> ClientUser: + return ClientUser(username=self.username, token=self.token) diff --git a/src/mobu/services/business/base.py b/src/mobu/services/business/base.py index f233f4d4..e01365be 100644 --- a/src/mobu/services/business/base.py +++ b/src/mobu/services/business/base.py @@ -185,7 +185,7 @@ async def idle(self) -> None: async def error_idle(self) -> None: """Pause after an error and before attempting to restart. - Thia is called directly by `~mobu.services.monkey.Monkey` rather than + This is called directly by `~mobu.services.monkey.Monkey` rather than by the business. It happens outside of `run` and therefore must handle acknowledging a shutdown request. """ diff --git a/src/mobu/services/business/notebookrunner.py b/src/mobu/services/business/notebookrunner.py index d2b55cac..335c2425 100644 --- a/src/mobu/services/business/notebookrunner.py +++ b/src/mobu/services/business/notebookrunner.py @@ -17,6 +17,8 @@ import yaml from httpx import AsyncClient +from rubin.nublado.client import JupyterLabSession +from rubin.nublado.client.models import CodeContext from structlog.stdlib import BoundLogger from ...config import config @@ -32,7 +34,6 @@ from ...models.repo import RepoConfig from ...models.user import AuthenticatedUser from ...storage.git import Git -from ...storage.nublado import JupyterLabSession from .nublado import NubladoBusiness __all__ = ["NotebookRunner"] @@ -299,7 +300,14 @@ async def execute_code(self, session: JupyterLabSession) -> None: cell_id = f'`{cell["id"]}` (#{cell["_index"]})' else: cell_id = f'#{cell["_index"]}' - await self.execute_cell(session, code, cell_id) + ctx = CodeContext( + notebook=self._notebook.name, + path=str(self._notebook), + cell=cell["id"], + cell_number=f"#{cell['_index']}", + cell_source=code, + ) + await self.execute_cell(session, code, cell_id, ctx) if not await self.execution_idle(): break @@ -310,14 +318,18 @@ async def execute_code(self, session: JupyterLabSession) -> None: break async def execute_cell( - self, session: JupyterLabSession, code: str, cell_id: str + self, + session: JupyterLabSession, + code: str, + cell_id: str, + context: CodeContext | None = None, ) -> None: if not self._notebook: raise RuntimeError("Executing a cell without a notebook") self.logger.info(f"Executing cell {cell_id}:\n{code}\n") with self.timings.start("execute_cell", self.annotations(cell_id)): self._running_code = code - reply = await session.run_python(code) + reply = await session.run_python(code, context=context) self._running_code = None self.logger.info(f"Result:\n{reply}\n") diff --git a/src/mobu/services/business/nublado.py b/src/mobu/services/business/nublado.py index e85c81ba..ad053cb8 100644 --- a/src/mobu/services/business/nublado.py +++ b/src/mobu/services/business/nublado.py @@ -11,19 +11,35 @@ from typing import Generic, TypeVar from httpx import AsyncClient +from rubin.nublado.client import JupyterLabSession, NubladoClient +from rubin.nublado.client.exceptions import ( + CodeExecutionError as ClientCodeExecutionError, +) +from rubin.nublado.client.exceptions import ( + JupyterProtocolError as ClientJupyterProtocolError, +) +from rubin.nublado.client.exceptions import ( + JupyterWebError as ClientJupyterWebError, +) from safir.datetime import current_datetime, format_datetime_for_logging from safir.slack.blockkit import SlackException from structlog.stdlib import BoundLogger from ...config import config -from ...exceptions import JupyterSpawnError, JupyterTimeoutError +from ...exceptions import ( + CodeExecutionError, + JupyterProtocolError, + JupyterSpawnError, + JupyterTimeoutError, + JupyterWebError, + MobuMixin, +) from ...models.business.nublado import ( NubladoBusinessData, NubladoBusinessOptions, RunningImage, ) from ...models.user import AuthenticatedUser -from ...storage.nublado import JupyterLabSession, NubladoClient from .base import Business T = TypeVar("T", bound="NubladoBusinessOptions") @@ -108,12 +124,11 @@ def __init__( raise RuntimeError("environment_url not set") environment_url = str(config.environment_url).rstrip("/") self._client = NubladoClient( - user=user, + user=user.to_client_user(), base_url=environment_url + options.url_prefix, logger=logger, timeout=options.jupyter_timeout, ) - self._image: RunningImage | None = None self._node: str | None = None self._random = SystemRandom() @@ -144,8 +159,12 @@ def annotations(self) -> dict[str, str]: and then add things to the resulting dictionary. """ result = {} - if self._image and self._image.description: - result["image"] = self._image.description + if self._image: + result["image"] = ( + self._image.description + or self._image.reference + or "" + ) if self._node: result["node"] = self._node return result @@ -166,6 +185,27 @@ async def startup(self) -> None: self.logger.warning(msg) async def execute(self) -> None: + try: + await self._execute() + except Exception as exc: + monkey = None + event = "execute_code" # Fallback + if isinstance(exc, MobuMixin): + if exc.monkey: + monkey = exc.monkey + if exc.event: + event = exc.event + if isinstance(exc, ClientCodeExecutionError): + raise CodeExecutionError.from_client_exception( + exc, + monkey=monkey, + event=event, + annotations=self.annotations(), + started_at=exc.started_at, + ) from exc + raise + + async def _execute(self) -> None: if self.options.delete_lab or await self._client.is_lab_stopped(): self._image = None if not await self.spawn_lab(): @@ -202,13 +242,36 @@ async def idle(self) -> None: async def hub_login(self) -> None: self.logger.info("Logging in to hub") - with self.timings.start("hub_login"): - await self._client.auth_to_hub() - - async def spawn_lab(self) -> bool: + with self.timings.start("hub_login", self.annotations()) as sw: + try: + await self._client.auth_to_hub() + except ClientJupyterProtocolError as exc: + raise JupyterProtocolError.from_client_exception( + exc, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from exc + except ClientJupyterWebError as exc: + raise JupyterWebError.from_client_exception( + exc, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from exc + + async def spawn_lab(self) -> bool: # noqa: C901 with self.timings.start("spawn_lab", self.annotations()) as sw: timeout = self.options.spawn_timeout - await self._client.spawn_lab(self.options.image) + try: + await self._client.spawn_lab(self.options.image) + except ClientJupyterWebError as exc: + raise JupyterWebError.from_client_exception( + exc, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from exc # Pause before using the progress API, since otherwise it may not # have attached to the spawner and will not return a full stream @@ -227,13 +290,32 @@ async def spawn_lab(self) -> bool: return True except TimeoutError: log = "\n".join([str(m) for m in log_messages]) - raise JupyterSpawnError(log, self.user.username) from None + raise JupyterSpawnError( + log, + self.user.username, + event=sw.event, + started_at=sw.start_time, + ) from None + except ClientJupyterWebError as exc: + raise JupyterWebError.from_client_exception( + exc, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from exc except SlackException: raise except Exception as e: log = "\n".join([str(m) for m in log_messages]) user = self.user.username - raise JupyterSpawnError.from_exception(log, e, user) from e + raise JupyterSpawnError.from_exception( + log, + e, + user, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from e # We only fall through if the spawn failed, timed out, or if we're # stopping the business. @@ -243,13 +325,32 @@ async def spawn_lab(self) -> bool: if sw.elapsed > timeout: elapsed_seconds = round(sw.elapsed.total_seconds()) msg = f"Lab did not spawn after {elapsed_seconds}s" - raise JupyterTimeoutError(msg, self.user.username, log) - raise JupyterSpawnError(log, self.user.username) + raise JupyterTimeoutError( + msg, + self.user.username, + log, + event=sw.event, + started_at=sw.start_time, + ) + raise JupyterSpawnError( + log, + self.user.username, + event=sw.event, + started_at=sw.start_time, + ) async def lab_login(self) -> None: self.logger.info("Logging in to lab") - with self.timings.start("lab_login", self.annotations()): - await self._client.auth_to_lab() + with self.timings.start("lab_login", self.annotations()) as sw: + try: + await self._client.auth_to_lab() + except ClientJupyterProtocolError as exc: + raise JupyterProtocolError.from_client_exception( + exc, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from exc @asynccontextmanager async def open_session( @@ -265,7 +366,9 @@ async def open_session( yield session await self.lab_login() self.logger.info("Deleting lab session") - stopwatch = self.timings.start("delete_sesion", self.annotations()) + stopwatch = self.timings.start( + "delete_session", self.annotations() + ) stopwatch.stop() self._node = None @@ -295,8 +398,16 @@ async def setup_session(self, session: JupyterLabSession) -> None: async def delete_lab(self) -> None: self.logger.info("Deleting lab") - with self.timings.start("delete_lab", self.annotations()): - await self._client.stop_lab() + with self.timings.start("delete_lab", self.annotations()) as sw: + try: + await self._client.stop_lab() + except ClientJupyterWebError as exc: + raise JupyterWebError.from_client_exception( + exc, + event=sw.event, + annotations=sw.annotations, + started_at=sw.start_time, + ) from exc if self.stopping: return @@ -310,7 +421,16 @@ async def delete_lab(self) -> None: if elapsed > self.options.delete_timeout: if not await self._client.is_lab_stopped(log_running=True): msg = f"Lab not deleted after {elapsed_seconds}s" - raise JupyterTimeoutError(msg, self.user.username) + jte = JupyterTimeoutError( + msg, + self.user.username, + started_at=start, + event=sw.event, + ) + jte.annotations["image"] = ( + self.options.image.description + ) + raise jte msg = f"Waiting for lab deletion ({elapsed_seconds}s elapsed)" self.logger.info(msg) if not await self.pause(timedelta(seconds=2)): diff --git a/src/mobu/services/business/nubladopythonloop.py b/src/mobu/services/business/nubladopythonloop.py index eb3aec28..787ea8ca 100644 --- a/src/mobu/services/business/nubladopythonloop.py +++ b/src/mobu/services/business/nubladopythonloop.py @@ -7,11 +7,11 @@ from __future__ import annotations from httpx import AsyncClient +from rubin.nublado.client import JupyterLabSession from structlog.stdlib import BoundLogger from ...models.business.nubladopythonloop import NubladoPythonLoopOptions from ...models.user import AuthenticatedUser -from ...storage.nublado import JupyterLabSession from .nublado import NubladoBusiness __all__ = ["NubladoPythonLoop"] diff --git a/src/mobu/services/business/tap.py b/src/mobu/services/business/tap.py index 00518667..5ec01ab7 100644 --- a/src/mobu/services/business/tap.py +++ b/src/mobu/services/business/tap.py @@ -84,6 +84,8 @@ async def execute(self) -> None: user=self.user.username, code=query, code_type="TAP query", + event="execute_query", + started_at=sw.start_time, error=f"{type(e).__name__}: {e!s}", ) from e diff --git a/src/mobu/services/monkey.py b/src/mobu/services/monkey.py index 81375131..13eaea9f 100644 --- a/src/mobu/services/monkey.py +++ b/src/mobu/services/monkey.py @@ -16,7 +16,7 @@ from structlog.stdlib import BoundLogger from ..config import config -from ..exceptions import MobuSlackException +from ..exceptions import MobuMixin from ..models.business.base import BusinessConfig from ..models.business.empty import EmptyLoopConfig from ..models.business.gitlfs import GitLFSConfig @@ -137,7 +137,11 @@ async def alert(self, exc: Exception) -> None: if not self._slack: self._logger.info("Alert hook isn't set, so not sending to Slack") return - + monkey = f"{self._flock}/{self._name}" if self._flock else self._name + if isinstance(exc, MobuMixin): + # Add the monkey info if it is not already set. + if not exc.monkey: + exc.monkey = monkey if isinstance(exc, SlackException): # Avoid post_exception here since it adds the application name, # but mobu (unusually) uses a dedicated web hook and therefore @@ -148,10 +152,6 @@ async def alert(self, exc: Exception) -> None: date = format_datetime_for_logging(now) name = type(exc).__name__ error = f"{name}: {exc!s}" - if self._flock: - monkey = f"{self._flock}/{self._name}" - else: - monkey = self._name message = SlackMessage( message=f"Unexpected exception {error}", fields=[ @@ -210,13 +210,16 @@ async def _runner(self) -> None: run = False except Exception as e: msg = "Exception thrown while doing monkey business" - if isinstance(e, MobuSlackException): - if self._flock: - e.monkey = f"{self._flock}/{self._name}" - else: - e.monkey = self._name - self._logger.exception(msg) + if self._flock: + monkey = f"{self._flock}/{self._name}" + else: + monkey = self._name + if isinstance(e, MobuMixin): + e.monkey = monkey + await self.alert(e) + self._logger.exception(msg) + run = self._restart and self._state == MonkeyState.RUNNING if run: self._state = MonkeyState.ERROR diff --git a/src/mobu/storage/nublado.py b/src/mobu/storage/nublado.py deleted file mode 100644 index 503c2d74..00000000 --- a/src/mobu/storage/nublado.py +++ /dev/null @@ -1,880 +0,0 @@ -"""AsyncIO client for communicating with Jupyter using Nublado. - -Allows the caller to login to JupyterHub, spawn lab containers, and then run -Jupyter kernels remotely. -""" - -from __future__ import annotations - -import asyncio -import json -from collections.abc import AsyncIterator, Callable, Coroutine -from dataclasses import dataclass -from datetime import timedelta -from functools import wraps -from types import TracebackType -from typing import Concatenate, Literal, ParamSpec, Self, TypeVar -from urllib.parse import urljoin, urlparse -from uuid import uuid4 - -from httpx import AsyncClient, Cookies, HTTPError, Response -from httpx_sse import EventSource, aconnect_sse -from safir.datetime import current_datetime -from structlog.stdlib import BoundLogger -from websockets.client import WebSocketClientProtocol -from websockets.client import connect as websocket_connect -from websockets.exceptions import WebSocketException - -from ..constants import WEBSOCKET_OPEN_TIMEOUT -from ..exceptions import ( - CodeExecutionError, - JupyterProtocolError, - JupyterTimeoutError, - JupyterWebError, - JupyterWebSocketError, -) -from ..models.business.nublado import NubladoImage -from ..models.user import AuthenticatedUser - -P = ParamSpec("P") -T = TypeVar("T") - -__all__ = ["NubladoClient", "JupyterLabSession"] - - -@dataclass(frozen=True, slots=True) -class SpawnProgressMessage: - """A progress message from lab spawning.""" - - progress: int - """Percentage progress on spawning.""" - - message: str - """A progress message.""" - - ready: bool - """Whether the server is ready.""" - - -class JupyterSpawnProgress: - """Async iterator returning spawn progress messages. - - This parses messages from the progress API, which is an EventStream API - that provides status messages for a spawning lab. - - Parameters - ---------- - event_source - Open EventStream connection. - logger - Logger to use. - """ - - def __init__(self, event_source: EventSource, logger: BoundLogger) -> None: - self._source = event_source - self._logger = logger - self._start = current_datetime(microseconds=True) - - async def __aiter__(self) -> AsyncIterator[SpawnProgressMessage]: - """Iterate over spawn progress events. - - Yields - ------ - SpawnProgressMessage - The next progress message. - - Raises - ------ - httpx.HTTPError - Raised if a protocol error occurred while connecting to the - EventStream API or reading or parsing a message from it. - """ - async for sse in self._source.aiter_sse(): - try: - event_dict = sse.json() - event = SpawnProgressMessage( - progress=event_dict["progress"], - message=event_dict["message"], - ready=event_dict.get("ready", False), - ) - except Exception as e: - err = f"{type(e).__name__}: {e!s}" - msg = f"Error parsing progress event, ignoring: {err}" - self._logger.warning(msg, type=sse.event, data=sse.data) - continue - - # Log the event and yield it. - now = current_datetime(microseconds=True) - elapsed = int((now - self._start).total_seconds()) - status = "complete" if event.ready else "in progress" - msg = f"Spawn {status} ({elapsed}s elapsed): {event.message}" - self._logger.info(msg, elapsed=elapsed, status=status) - yield event - - -@dataclass(frozen=True, slots=True) -class JupyterOutput: - """Output from a Jupyter lab kernel. - - Parsing WebSocket messages will result in a stream of these objects with - partial output, ending in a final one with the ``done`` flag set. - """ - - content: str - """Partial output from code execution (may be empty).""" - - done: bool = False - """Whether this indicates the end of execution.""" - - -class JupyterLabSession: - """Represents an open session with a Jupyter Lab. - - A context manager providing an open WebSocket session. The session will be - automatically deleted when exiting the context manager. Objects of this - type should be created by calling `NubladoClient.open_lab_session`. - - Parameters - ---------- - username - User the session is for. - base_url - Base URL for talking to JupyterHub or the lab (via the proxy). - kernel_name - Name of the kernel to use for the session. - nobebook_name - Name of the notebook we will be running, which is passed to the - session and might influence logging on the lab side. If set, the - session type will be set to ``notebook``. If not set, the session type - will be set to ``console``. - http_client - HTTP client to talk to the Jupyter lab. - logger - Logger to use. - """ - - _IGNORED_MESSAGE_TYPES = ( - "comm_close", - "comm_msg", - "comm_open", - "display_data", - "execute_input", - "execute_result", - "status", - ) - """WebSocket messge types ignored by the parser. - - Jupyter labs send a lot of types of WebSocket messages to provide status - or display formatted results. For our purposes, we only care about output - and errors, but we want to warn about unrecognized messages so that we - notice places where we may be missing part of the protocol. These are - message types that we know we don't care about and should ignore. - """ - - def __init__( - self, - *, - username: str, - base_url: str, - kernel_name: str = "LSST", - notebook_name: str | None = None, - max_websocket_size: int | None, - http_client: AsyncClient, - xsrf: str | None, - logger: BoundLogger, - ) -> None: - self._username = username - self._base_url = base_url - self._kernel_name = kernel_name - self._notebook_name = notebook_name - self._max_websocket_size = max_websocket_size - self._client = http_client - self._xsrf = xsrf - self._logger = logger - - self._session_id: str | None = None - self._socket: WebSocketClientProtocol | None = None - - async def __aenter__(self) -> Self: - """Create the session and open the WebSocket connection. - - Raises - ------ - JupyterTimeoutError - Raised if the attempt to open a WebSocket to the lab timed out. - JupyterWebError - Raised if an error occurred while creating the lab session. - JupyterWebSocketError - Raised if a protocol or network error occurred while trying to - create the WebSocket. - """ - # This class implements an explicit context manager instead of using - # an async generator and contextlib.asynccontextmanager, and similarly - # explicitly calls the __aenter__ and __aexit__ methods in the - # WebSocket library rather than using it as a context manager. - # - # Initially, it was implemented as a generator, but when using that - # approach the code after the yield in the generator was called at an - # arbitrary time in the future, rather than when the context manager - # exited. This meant that it was often called after the httpx client - # had been closed, which meant it was unable to delete the lab session - # and raised background exceptions. This approach allows more explicit - # control of when the context manager is shut down and ensures it - # happens immediately when the context exits. - username = self._username - notebook = self._notebook_name - url = self._url_for(f"user/{username}/api/sessions") - body = { - "kernel": {"name": self._kernel_name}, - "name": notebook or "(no notebook)", - "path": notebook if notebook else uuid4().hex, - "type": "notebook" if notebook else "console", - } - headers = {} - if self._xsrf: - headers["X-XSRFToken"] = self._xsrf - try: - r = await self._client.post(url, json=body, headers=headers) - r.raise_for_status() - except HTTPError as e: - raise JupyterWebError.from_exception(e, self._username) from e - response = r.json() - self._session_id = response["id"] - kernel = response["kernel"]["id"] - - # Build a request for the same URL using httpx so that it will - # generate request headers, and copy select headers required for - # authentication into the WebSocket call. - url = self._url_for(f"user/{username}/api/kernels/{kernel}/channels") - request = self._client.build_request("GET", url) - headers = {h: request.headers[h] for h in ("authorization", "cookie")} - if self._xsrf: - headers["X-XSRFToken"] = self._xsrf - - # Open the WebSocket connection using those headers. - self._logger.debug("Opening WebSocket connection") - start = current_datetime(microseconds=True) - try: - self._socket = await websocket_connect( - self._url_for_websocket(url), - extra_headers=headers, - open_timeout=WEBSOCKET_OPEN_TIMEOUT, - max_size=self._max_websocket_size, - ).__aenter__() - except WebSocketException as e: - user = self._username - raise JupyterWebSocketError.from_exception(e, user) from e - except TimeoutError as e: - msg = "Timed out attempting to open WebSocket to lab session" - user = self._username - raise JupyterTimeoutError(msg, user, started_at=start) from e - return self - - async def __aexit__( - self, - exc_type: type[BaseException] | None, - exc_val: BaseException | None, - exc_tb: TracebackType | None, - ) -> Literal[False]: - """Shut down the open WebSocket and delete the session.""" - username = self._username - session_id = self._session_id - - # Close the WebSocket. - if self._socket: - try: - await self._socket.close() - except WebSocketException as e: - raise JupyterWebSocketError.from_exception(e, username) from e - self._socket = None - - # Delete the lab session. - url = self._url_for(f"user/{username}/api/sessions/{session_id}") - headers = {} - if self._xsrf: - headers["X-XSRFToken"] = self._xsrf - try: - r = await self._client.delete(url, headers=headers) - r.raise_for_status() - except HTTPError as e: - # Be careful to not raise an exception if we're already processing - # an exception, since the exception from inside the context - # manager is almost certainly more interesting than the exception - # from closing the lab session. - if exc_type: - self._logger.exception("Failed to close session") - else: - raise JupyterWebError.from_exception(e, self._username) from e - - return False - - async def run_python(self, code: str) -> str: - """Run a block of Python code in a Jupyter lab kernel. - - Parameters - ---------- - code - Code to run. - - Returns - ------- - str - Output from the kernel. - - Raises - ------ - JupyterCodeExecutionError - Raised if an error was reported by the Jupyter lab kernel. - JupyterWebSocketError - Raised if there was a WebSocket protocol error while running code - or waiting for the response. - RuntimeError - Raised if called before entering the context and thus before - creating the WebSocket session. - """ - if not self._socket: - raise RuntimeError("JupyterLabSession not opened") - message_id = uuid4().hex - request = { - "header": { - "username": self._username, - "version": "5.0", - "session": self._session_id, - "msg_id": message_id, - "msg_type": "execute_request", - }, - "parent_header": {}, - "channel": "shell", - "content": { - "code": code, - "silent": False, - "store_history": False, - "user_expressions": {}, - "allow_stdin": False, - }, - "metadata": {}, - "buffers": {}, - } - - # Send the message and consume messages waiting for the response. - result = "" - try: - await self._socket.send(json.dumps(request)) - async for message in self._socket: - try: - output = self._parse_message(message, message_id) - except CodeExecutionError as e: - e.code = code - raise - except Exception as e: - error = f"{type(e).__name__}: {e!s}" - msg = "Ignoring unparsable web socket message" - self._logger.warning(msg, error=error, message=message) - - # Accumulate the results if they are of interest, and exit and - # return the results if this message indicated the end of - # execution. - if not output: - continue - result += output.content - if output.done: - break - except WebSocketException as e: - user = self._username - raise JupyterWebSocketError.from_exception(e, user) from e - - # Return the accumulated output. - return result - - def _parse_message( - self, message: str | bytes, message_id: str - ) -> JupyterOutput | None: - """Parse a WebSocket message from a Jupyter lab kernel. - - Parameters - ---------- - message - Raw message. - message_id - Message ID of the message we went, so that we can look for - replies. - - Returns - ------- - JupyterOutput or None - Parsed message, or `None` if the message wasn't of interest. - - Raises - ------ - KeyError - Raised if the WebSocket message wasn't in the expected format. - """ - if isinstance(message, bytes): - message = message.decode() - data = json.loads(message) - self._logger.debug("Received kernel message", message=data) - - # Ignore headers not intended for us. Thie web socket is rather - # chatty with broadcast status messages. - if data.get("parent_header", {}).get("msg_id") != message_id: - return None - - # Analyse the message type to figure out what to do with the response. - msg_type = data["msg_type"] - if msg_type in self._IGNORED_MESSAGE_TYPES: - return None - elif msg_type == "stream": - return JupyterOutput(content=data["content"]["text"]) - elif msg_type == "execute_reply": - status = data["content"]["status"] - if status == "ok": - return JupyterOutput(content="", done=True) - else: - raise CodeExecutionError(user=self._username, status=status) - elif msg_type == "error": - error = "".join(data["content"]["traceback"]) - raise CodeExecutionError(user=self._username, error=error) - else: - msg = "Ignoring unrecognized WebSocket message" - self._logger.warning(msg, message_type=msg_type, message=data) - return None - - def _url_for(self, partial: str) -> str: - """Construct a JupyterHub or Jupyter lab URL from a partial URL. - - Parameters - ---------- - partial - Part of the URL after the prefix for JupyterHub. - - Returns - ------- - str - Full URL to use. - """ - if self._base_url.endswith("/"): - return f"{self._base_url}{partial}" - else: - return f"{self._base_url}/{partial}" - - def _url_for_websocket(self, url: str) -> str: - """Convert a URL to a WebSocket URL. - - Parameters - ---------- - url - Regular HTTP URL. - - Returns - ------- - str - URL converted to the ``wss`` scheme. - """ - return urlparse(url)._replace(scheme="wss").geturl() - - -def _convert_exception( - f: Callable[Concatenate[NubladoClient, P], Coroutine[None, None, T]], -) -> Callable[Concatenate[NubladoClient, P], Coroutine[None, None, T]]: - """Convert web errors to a `~mobu.exceptions.JupyterWebError`. - - This can only be used as a decorator on `JupyterClientSession` or another - object that has a ``user`` property containing an - `~mobu.models.user.AuthenticatedUser`. - """ - - @wraps(f) - async def wrapper( - client: NubladoClient, *args: P.args, **kwargs: P.kwargs - ) -> T: - try: - return await f(client, *args, **kwargs) - except HTTPError as e: - username = client.user.username - raise JupyterWebError.from_exception(e, username) from e - - return wrapper - - -def _convert_iterator_exception( - f: Callable[Concatenate[NubladoClient, P], AsyncIterator[T]], -) -> Callable[Concatenate[NubladoClient, P], AsyncIterator[T]]: - """Convert web errors to a `~mobu.exceptions.JupyterWebError`. - - This can only be used as a decorator on `JupyterClientSession` or another - object that has a ``user`` property containing an - `~mobu.models.user.AuthenticatedUser`. - """ - - @wraps(f) - async def wrapper( - client: NubladoClient, *args: P.args, **kwargs: P.kwargs - ) -> AsyncIterator[T]: - try: - async for result in f(client, *args, **kwargs): - yield result - except HTTPError as e: - username = client.user.username - raise JupyterWebError.from_exception(e, username) from e - - return wrapper - - -class NubladoClient: - """Client for talking to JupyterHub and Jupyter labs that use Nublado. - - Parameters - ---------- - user - User as which to authenticate. - base_url - Base URL for JupyterHub and the proxy to talk to the labs. - logger - Logger to use. - timeout - Timeout to use when talking to JupyterHub and Jupyter lab. This is - used as a connection, read, and write timeout for all regular HTTP - calls. - - Notes - ----- - This class creates its own `httpx.AsyncClient` for each instance, separate - from the one used by the rest of the application, since it needs to - isolate the cookies set by JupyterHub and the lab from those for any other - user. - """ - - def __init__( - self, - *, - user: AuthenticatedUser, - base_url: str, - logger: BoundLogger, - timeout: timedelta = timedelta(seconds=30), - ) -> None: - self.user = user - self._base_url = base_url - self._logger = logger.bind(user=user.username) - - # Construct a connection pool to use for requests to JupyterHub. We - # have to create a separate connection pool for every monkey, since - # each will get user-specific cookies set by JupyterHub. If we shared - # connection pools, monkeys would overwrite each other's cookies and - # get authentication failures from labs. - headers = {"Authorization": f"Bearer {user.token}"} - self._client = AsyncClient( - follow_redirects=True, - headers=headers, - timeout=timeout.total_seconds(), - ) - self._hub_xsrf: str | None = None - self._lab_xsrf: str | None = None - self._logger.debug("Created new NubladoClient") - - async def close(self) -> None: - """Close the underlying HTTP connection pool.""" - await self._client.aclose() - - @_convert_exception - async def auth_to_hub(self) -> None: - """Retrieve the JupyterHub home page. - - This forces a refresh of the authentication cookies set in the client - session, which may be required to use API calls that return 401 errors - instead of redirecting the user to log in. - - The reply will set an ``_xsrf`` cookie that must be lifted into the - headers for subsequent requests. - - Raises - ------ - JupyterProtocolError - Raised if no ``_xsrf`` cookie was set in the reply from the lab. - """ - url = self._url_for("hub/home") - r = await self._client.get(url, follow_redirects=False) - # As with auth_to_lab, manually extract from cookies at each - # redirection, because httpx doesn't do that if following redirects - # automatically. - while r.is_redirect: - self._logger.debug( - "Following hub redirect looking for _xsrf cookies", - method=r.request.method, - url=r.url.copy_with(query=None, fragment=None), - status_code=r.status_code, - ) - xsrf = self._extract_xsrf(r) - if xsrf and xsrf != self._hub_xsrf: - self._hub_xsrf = xsrf - self._logger.debug( - "Set _hub_xsrf", - url=r.url.copy_with(query=None, fragment=None), - status_code=r.status_code, - ) - next_url = urljoin(url, r.headers["Location"]) - r = await self._client.get(next_url, follow_redirects=False) - r.raise_for_status() - xsrf = self._extract_xsrf(r) - if xsrf and xsrf != self._hub_xsrf: - self._hub_xsrf = xsrf - self._logger.debug( - "Set _hub_xsrf", - url=r.url.copy_with(query=None, fragment=None), - status_code=r.status_code, - ) - elif not self._hub_xsrf: - msg = "No _xsrf cookie set in login reply from JupyterHub" - raise JupyterProtocolError(msg) - - @_convert_exception - async def auth_to_lab(self) -> None: - """Authenticate to the Jupyter lab. - - Request the top-level lab page, which will force the OpenID Connect - authentication with JupyterHub and set authentication cookies. This is - required before making API calls to the lab, such as running code. - - The reply will set an ``_xsrf`` cookie that must be lifted into the - headers for subsequent requests. - - Setting ``Sec-Fetch-Mode`` is not currently required, but it - suppresses an annoying error message in the lab logs. - - Raises - ------ - JupyterProtocolError - Raised if no ``_xsrf`` cookie was set in the reply from the lab. - """ - url = self._url_for(f"user/{self.user.username}/lab") - headers = {"Sec-Fetch-Mode": "navigate"} - r = await self._client.get( - url, headers=headers, follow_redirects=False - ) - while r.is_redirect: - self._logger.debug( - "Following lab redirect looking for _xsrf cookies", - method=r.request.method, - url=r.url.copy_with(query=None, fragment=None), - status_code=r.status_code, - ) - xsrf = self._extract_xsrf(r) - if xsrf and xsrf != self._lab_xsrf: - self._lab_xsrf = xsrf - self._logger.debug( - "Set _lab_xsrf", - url=r.url.copy_with(query=None, fragment=None), - status_code=r.status_code, - ) - next_url = urljoin(url, r.headers["Location"]) - r = await self._client.get( - next_url, headers=headers, follow_redirects=False - ) - r.raise_for_status() - xsrf = self._extract_xsrf(r) - if xsrf and xsrf != self._lab_xsrf: - self._lab_xsrf = xsrf - self._logger.debug( - "Set _lab_xsrf", - url=r.url.copy_with(query=None, fragment=None), - status_code=r.status_code, - ) - if not self._lab_xsrf: - msg = "No _xsrf cookie set in login reply from lab" - raise JupyterProtocolError(msg) - - @_convert_exception - async def is_lab_stopped(self, *, log_running: bool = False) -> bool: - """Determine if the lab is fully stopped. - - Parameters - ---------- - log_running - Log a warning with additional information if the lab still - exists. - """ - url = self._url_for(f"hub/api/users/{self.user.username}") - headers = {"Referer": self._url_for("hub/home")} - if self._hub_xsrf: - headers["X-XSRFToken"] = self._hub_xsrf - r = await self._client.get(url, headers=headers) - r.raise_for_status() - - # We currently only support a single lab per user, so the lab is - # running if and only if the server data for the user is not empty. - data = r.json() - result = data["servers"] == {} - if log_running and not result: - msg = "User API data still shows running lab" - self._logger.warning(msg, servers=data["servers"]) - return result - - def open_lab_session( - self, - notebook_name: str | None = None, - *, - max_websocket_size: int | None = None, - kernel_name: str = "LSST", - ) -> JupyterLabSession: - """Open a Jupyter lab session. - - Returns a context manager object so must be called via ``async with`` - or the equivalent. The lab session will automatically be deleted when - the context manager exits. - - Parameters - ---------- - notebook_name - Name of the notebook we will be running, which is passed to the - session and might influence logging on the lab side. If set, the - session type will be set to ``notebook``. If not set, the session - type will be set to ``console``. - max_websocket_size - Maximum size of a WebSocket message, or `None` for no limit. - kernel_name - Name of the kernel to use for the session. - - Returns - ------- - JupyterLabSession - Context manager to open the WebSocket session. - """ - return JupyterLabSession( - username=self.user.username, - base_url=self._base_url, - kernel_name=kernel_name, - notebook_name=notebook_name, - max_websocket_size=max_websocket_size, - http_client=self._client, - xsrf=self._lab_xsrf, - logger=self._logger, - ) - - @_convert_exception - async def spawn_lab(self, config: NubladoImage) -> None: - """Spawn a Jupyter lab pod. - - Parameters - ---------- - config - Image configuration. - - Raises - ------ - JupyterWebError - Raised if an error occurred talking to JupyterHub. - """ - url = self._url_for("hub/spawn") - data = config.to_spawn_form() - - # Retrieving the spawn page before POSTing to it appears to trigger - # some necessary internal state construction (and also more accurately - # simulates a user interaction). See DM-23864. - headers = {} - if self._hub_xsrf: - headers["X-XSRFToken"] = self._hub_xsrf - r = await self._client.get(url, headers=headers) - r.raise_for_status() - - # POST the options form to the spawn page. This should redirect to - # the spawn-pending page, which will return a 200. - self._logger.info("Spawning lab image", user=self.user.username) - r = await self._client.post(url, headers=headers, data=data) - r.raise_for_status() - - @_convert_exception - async def stop_lab(self) -> None: - """Stop the user's Jupyter lab.""" - if await self.is_lab_stopped(): - self._logger.info("Lab is already stopped") - return - url = self._url_for(f"hub/api/users/{self.user.username}/server") - headers = {"Referer": self._url_for("hub/home")} - if self._hub_xsrf: - headers["X-XSRFToken"] = self._hub_xsrf - r = await self._client.delete(url, headers=headers) - r.raise_for_status() - - @_convert_iterator_exception - async def watch_spawn_progress( - self, - ) -> AsyncIterator[SpawnProgressMessage]: - """Monitor lab spawn progress. - - This is an EventStream API, which provides a stream of events until - the lab is spawned or the spawn fails. - - Yields - ------ - SpawnProgressMessage - Next progress message from JupyterHub. - """ - client = self._client - username = self.user.username - url = self._url_for(f"hub/api/users/{username}/server/progress") - headers = {"Referer": self._url_for("hub/home")} - if self._hub_xsrf: - headers["X-XSRFToken"] = self._hub_xsrf - while True: - async with aconnect_sse(client, "GET", url, headers=headers) as s: - async for message in JupyterSpawnProgress(s, self._logger): - yield message - - # Sometimes we get only the initial request message and then the - # progress API immediately closes the connection. If that happens, - # try reconnecting to the progress stream after a short delay. I - # beleive this was a bug in kubespawner, so once we've switched to - # the lab controller everywhere, we can probably drop this code. - if message.progress > 0: - break - await asyncio.sleep(1) - self._logger.info("Retrying spawn progress request") - - def _extract_xsrf(self, response: Response) -> str | None: - """Extract the XSRF token from the cookies in a response. - - Parameters - ---------- - response - Response from a Jupyter server. - - Returns - ------- - str or None - Extracted XSRF value or `None` if none was present. - """ - cookies = Cookies() - cookies.extract_cookies(response) - xsrf = cookies.get("_xsrf") - if xsrf is not None: - self._logger.debug( - "Extracted _xsrf cookie", - method=response.request.method, - url=response.url.copy_with(query=None, fragment=None), - status_code=response.status_code, - # Logging the set-cookie header can be useful here but it - # leaks secrets. Don't put that code in a release build. - ) - return xsrf - - def _url_for(self, partial: str) -> str: - """Construct a JupyterHub or Jupyter lab URL from a partial URL. - - Parameters - ---------- - partial - Part of the URL after the prefix for JupyterHub. - - Returns - ------- - str - Full URL to use. - """ - if self._base_url.endswith("/"): - return f"{self._base_url}{partial}" - else: - return f"{self._base_url}/{partial}" - - def _url_for_lab_websocket(self, username: str, kernel: str) -> str: - """Build the URL for the WebSocket to a lab kernel.""" - url = self._url_for(f"user/{username}/api/kernels/{kernel}/channels") - return urlparse(url)._replace(scheme="wss").geturl() diff --git a/tests/autostart_test.py b/tests/autostart_test.py index 0ef8b961..8744053c 100644 --- a/tests/autostart_test.py +++ b/tests/autostart_test.py @@ -9,11 +9,11 @@ import pytest import respx from httpx import AsyncClient +from rubin.nublado.client.testing import MockJupyter from mobu.config import config from .support.gafaelfawr import mock_gafaelfawr -from .support.jupyter import MockJupyter from .support.util import wait_for_flock_start AUTOSTART_CONFIG = """ @@ -136,10 +136,8 @@ async def test_autostart(client: AsyncClient, jupyter: MockJupyter) -> None: "business": { "failure_count": 0, "image": { - "description": "Recommended (Weekly 2077_43)", - "reference": ( - "lighthouse.ceres/library/sketchbook:recommended" - ), + "description": ANY, + "reference": ANY, }, "name": "NubladoPythonLoop", "refreshing": False, @@ -160,10 +158,8 @@ async def test_autostart(client: AsyncClient, jupyter: MockJupyter) -> None: "business": { "failure_count": 0, "image": { - "description": "Recommended (Weekly 2077_43)", - "reference": ( - "lighthouse.ceres/library/sketchbook:recommended" - ), + "description": ANY, + "reference": ANY, }, "name": "NubladoPythonLoop", "refreshing": False, diff --git a/tests/business/notebookrunner_test.py b/tests/business/notebookrunner_test.py index 9ad8e7d8..a584bc0c 100644 --- a/tests/business/notebookrunner_test.py +++ b/tests/business/notebookrunner_test.py @@ -10,13 +10,13 @@ import pytest import respx from httpx import AsyncClient +from rubin.nublado.client.testing import MockJupyter from safir.testing.slack import MockSlackWebhook from mobu.storage.git import Git from ..support.constants import TEST_DATA_DIR from ..support.gafaelfawr import mock_gafaelfawr -from ..support.jupyter import MockJupyter from ..support.util import wait_for_business, wait_for_log_message @@ -731,12 +731,12 @@ async def test_invalid_repo_config( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -834,10 +834,8 @@ async def test_alert( "business": { "failure_count": 1, "image": { - "description": "Recommended (Weekly 2077_43)", - "reference": ( - "lighthouse.ceres/library/sketchbook:recommended" - ), + "description": ANY, + "reference": ANY, }, "name": "NotebookRunner", "notebook": "exception.ipynb", @@ -862,7 +860,10 @@ async def test_alert( "type": "section", "text": { "type": "mrkdwn", - "text": "Error while running `exception.ipynb`", + "text": ( + "Error while running `exception.ipynb`" + " cell `ed399c0a`" + ), "verbatim": True, }, }, @@ -878,22 +879,22 @@ async def test_alert( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Image*\nRecommended (Weekly 2077_43)", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nexecute_cell", + "text": "*Event*\nexecute_code", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Image*\nRecommended (Weekly 2077_43)", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -902,7 +903,7 @@ async def test_alert( "type": "section", "text": { "type": "mrkdwn", - "text": "*Node*\nsome-node", + "text": "*Node*\nNode1", "verbatim": True, }, }, diff --git a/tests/business/nubladopythonloop_test.py b/tests/business/nubladopythonloop_test.py index 5b4f83b7..e6c671f7 100644 --- a/tests/business/nubladopythonloop_test.py +++ b/tests/business/nubladopythonloop_test.py @@ -10,12 +10,16 @@ import pytest import respx from httpx import AsyncClient +from rubin.nublado.client.testing import ( + JupyterAction, + JupyterState, + MockJupyter, +) from safir.testing.slack import MockSlackWebhook from mobu.config import config from ..support.gafaelfawr import mock_gafaelfawr -from ..support.jupyter import JupyterAction, JupyterState, MockJupyter from ..support.util import wait_for_business @@ -228,17 +232,17 @@ async def test_hub_failed( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser2", + "text": "*User*\nbot-mobu-testuser2", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser2", + "text": "*Event*\nspawn_lab", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nspawn_lab", + "text": "*Monkey*\ntest/bot-mobu-testuser2", "verbatim": True, }, ], @@ -293,6 +297,7 @@ async def test_redirect_loop( str(config.environment_url), "/nb/hub/api/users/bot-mobu-testuser1/server/progress", ) + assert slack.messages == [ { "blocks": [ @@ -319,17 +324,17 @@ async def test_redirect_loop( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Event*\nspawn_lab", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nspawn_lab", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -401,17 +406,17 @@ async def test_spawn_timeout( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Event*\nspawn_lab", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nspawn_lab", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -475,17 +480,17 @@ async def test_spawn_failed( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Event*\nspawn_lab", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nspawn_lab", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -566,11 +571,6 @@ async def test_delete_timeout( "text": "*Exception type*\nJupyterTimeoutError", "verbatim": True, }, - { - "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", - "verbatim": True, - }, { "type": "mrkdwn", "text": "*User*\nbot-mobu-testuser1", @@ -583,7 +583,7 @@ async def test_delete_timeout( }, { "type": "mrkdwn", - "text": "*Image*\nRecommended (Weekly 2077_43)", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -647,12 +647,12 @@ async def test_code_exception( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Image*\nRecommended (Weekly 2077_43)", "verbatim": True, }, { @@ -662,7 +662,7 @@ async def test_code_exception( }, { "type": "mrkdwn", - "text": "*Image*\nRecommended (Weekly 2077_43)", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -671,7 +671,7 @@ async def test_code_exception( "type": "section", "text": { "type": "mrkdwn", - "text": "*Node*\nsome-node", + "text": "*Node*\nNode1", "verbatim": True, }, }, @@ -727,13 +727,6 @@ async def test_long_error( "type": "NubladoPythonLoop", "options": { "code": "long_error_for_test()", - "image": { - "image_class": "by-reference", - "reference": ( - "registry.hub.docker.com/lsstsqre/sciplat-lab" - ":d_2021_08_30" - ), - }, "spawn_settle_time": 0, "max_executions": 1, }, @@ -748,9 +741,7 @@ async def test_long_error( # Check the lab form. assert jupyter.lab_form["bot-mobu-testuser1"] == { - "image_list": ( - "registry.hub.docker.com/lsstsqre/sciplat-lab:d_2021_08_30" - ), + "image_class": "recommended", "size": "Large", } @@ -783,12 +774,12 @@ async def test_long_error( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Image*\nRecommended (Weekly 2077_43)", "verbatim": True, }, { @@ -798,7 +789,7 @@ async def test_long_error( }, { "type": "mrkdwn", - "text": "*Image*\nRecommended (Weekly 2077_43)", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -807,7 +798,7 @@ async def test_long_error( "type": "section", "text": { "type": "mrkdwn", - "text": "*Node*\nsome-node", + "text": "*Node*\nNode1", "verbatim": True, }, }, @@ -1003,12 +994,12 @@ async def test_ansi_error( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Image*\nRecommended (Weekly 2077_43)", "verbatim": True, }, { @@ -1018,7 +1009,7 @@ async def test_ansi_error( }, { "type": "mrkdwn", - "text": "*Image*\nRecommended (Weekly 2077_43)", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], @@ -1027,7 +1018,7 @@ async def test_ansi_error( "type": "section", "text": { "type": "mrkdwn", - "text": "*Node*\nsome-node", + "text": "*Node*\nNode1", "verbatim": True, }, }, diff --git a/tests/business/tapquerysetrunner_test.py b/tests/business/tapquerysetrunner_test.py index 966abe70..2a1371eb 100644 --- a/tests/business/tapquerysetrunner_test.py +++ b/tests/business/tapquerysetrunner_test.py @@ -124,17 +124,17 @@ async def test_setup_error( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-tapuser", + "text": "*User*\nbot-mobu-tapuser", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-tapuser", + "text": "*Event*\nmake_client", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nmake_client", + "text": "*Monkey*\ntest/bot-mobu-tapuser", "verbatim": True, }, ], @@ -193,17 +193,17 @@ async def test_alert( }, { "type": "mrkdwn", - "text": "*Monkey*\ntest/bot-mobu-testuser1", + "text": "*User*\nbot-mobu-testuser1", "verbatim": True, }, { "type": "mrkdwn", - "text": "*User*\nbot-mobu-testuser1", + "text": "*Event*\nexecute_query", "verbatim": True, }, { "type": "mrkdwn", - "text": "*Event*\nexecute_query", + "text": "*Monkey*\ntest/bot-mobu-testuser1", "verbatim": True, }, ], diff --git a/tests/conftest.py b/tests/conftest.py index a5748a7c..86824b62 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,21 +6,34 @@ from contextlib import asynccontextmanager from importlib import reload from pathlib import Path +from tempfile import TemporaryDirectory from textwrap import dedent from unittest.mock import DEFAULT, patch import pytest import pytest_asyncio import respx +import safir.logging +import structlog from asgi_lifespan import LifespanManager from fastapi import FastAPI from httpx import ASGITransport, AsyncClient from pydantic import HttpUrl +from rubin.nublado.client import NubladoClient +from rubin.nublado.client.models import User +from rubin.nublado.client.testing import ( + MockJupyter, + MockJupyterWebSocket, + mock_jupyter, + mock_jupyter_websocket, +) from safir.testing.slack import MockSlackWebhook, mock_slack_webhook +from structlog.stdlib import BoundLogger from mobu import main from mobu.config import config from mobu.services.business.gitlfs import GitLFSBusiness +from mobu.services.business.nublado import _GET_IMAGE, _GET_NODE from .support.constants import ( TEST_BASE_URL, @@ -35,16 +48,31 @@ uninstall_git_lfs, verify_uuid_contents, ) -from .support.jupyter import ( - MockJupyter, - MockJupyterWebSocket, - mock_jupyter, - mock_jupyter_websocket, -) @pytest.fixture(autouse=True) -def _configure() -> Iterator[None]: +def environment_url() -> str: + return TEST_BASE_URL + + +@pytest.fixture +def test_user() -> User: + uname = "someuser" + return User(username=uname, token=make_gafaelfawr_token(uname)) + + +@pytest.fixture +def configured_logger() -> BoundLogger: + safir.logging.configure_logging( + name="nublado-client", + profile=safir.logging.Profile.development, + log_level=safir.logging.LogLevel.DEBUG, + ) + return structlog.get_logger("nublado-client") + + +@pytest.fixture(autouse=True) +def _configure(environment_url: str) -> Iterator[None]: """Set minimal configuration settings. Add an environment URL for testing purposes and create a Gafaelfawr admin @@ -54,7 +82,7 @@ def _configure() -> Iterator[None]: minimal test configuration and a unique admin token that is replaced after the test runs. """ - config.environment_url = HttpUrl("https://test.example.com") + config.environment_url = HttpUrl(environment_url) config.gafaelfawr_token = make_gafaelfawr_token() config.available_services = {"some_service", "some_other_service"} yield @@ -63,6 +91,12 @@ def _configure() -> Iterator[None]: config.available_services = set() +@pytest.fixture +def test_filesystem() -> Iterator[Path]: + with TemporaryDirectory() as td: + yield Path(td) + + @pytest.fixture def _enable_github_ci_app( tmp_path: Path, monkeypatch: pytest.MonkeyPatch @@ -159,13 +193,39 @@ async def app(jupyter: MockJupyter) -> AsyncIterator[FastAPI]: yield main.app +@pytest.fixture +def configured_nublado_client( + app: FastAPI, + environment_url: str, + configured_logger: BoundLogger, + test_user: User, + test_filesystem: Path, + jupyter: MockJupyter, +) -> NubladoClient: + n_client = NubladoClient( + user=test_user, logger=configured_logger, base_url=environment_url + ) + # For the test client, we also have to add the two headers that would + # be added by a GafaelfawrIngress in real life. + n_client._client.headers["X-Auth-Request-User"] = test_user.username + n_client._client.headers["X-Auth-Request-Token"] = test_user.token + return n_client + + @pytest_asyncio.fixture -async def client(app: FastAPI) -> AsyncIterator[AsyncClient]: +async def client( + app: FastAPI, + test_user: User, + jupyter: MockJupyter, +) -> AsyncIterator[AsyncClient]: """Return an ``httpx.AsyncClient`` configured to talk to the test app.""" async with AsyncClient( transport=ASGITransport(app=app), base_url=TEST_BASE_URL, - headers={"X-Auth-Request-User": "someuser"}, + headers={ + "X-Auth-Request-User": test_user.username, + "X-Auth-Request-Token": test_user.token, + }, ) as client: yield client @@ -183,9 +243,15 @@ async def anon_client(app: FastAPI) -> AsyncIterator[AsyncClient]: @pytest.fixture -def jupyter(respx_mock: respx.Router) -> Iterator[MockJupyter]: +def jupyter( + respx_mock: respx.Router, + environment_url: str, + test_filesystem: Path, +) -> Iterator[MockJupyter]: """Mock out JupyterHub and Jupyter labs.""" - jupyter_mock = mock_jupyter(respx_mock) + jupyter_mock = mock_jupyter( + respx_mock, base_url=environment_url, user_dir=test_filesystem + ) # respx has no mechanism to mock aconnect_ws, so we have to do it # ourselves. @@ -198,8 +264,17 @@ async def mock_connect( ) -> AsyncIterator[MockJupyterWebSocket]: yield mock_jupyter_websocket(url, extra_headers, jupyter_mock) - with patch("mobu.storage.nublado.websocket_connect") as mock: + with patch("rubin.nublado.client.nubladoclient.websocket_connect") as mock: mock.side_effect = mock_connect + # Register some code we call over and over and over... + jupyter_mock.register_python_result(_GET_NODE, "Node1") + jupyter_mock.register_python_result( + _GET_IMAGE, + ( + "lighthouse.ceres/library/sketchbook:recommended\n" + "Recommended (Weekly 2077_43)\n" + ), + ) yield jupyter_mock diff --git a/tests/handlers/solitary_test.py b/tests/handlers/solitary_test.py index faa424aa..7cc30cd1 100644 --- a/tests/handlers/solitary_test.py +++ b/tests/handlers/solitary_test.py @@ -7,6 +7,7 @@ import pytest import respx from httpx import AsyncClient +from rubin.nublado.client.testing import MockJupyter from safir.testing.slack import MockSlackWebhook from ..support.gafaelfawr import mock_gafaelfawr @@ -33,7 +34,10 @@ async def test_run(client: AsyncClient, respx_mock: respx.Router) -> None: @pytest.mark.asyncio async def test_error( - client: AsyncClient, slack: MockSlackWebhook, respx_mock: respx.Router + client: AsyncClient, + slack: MockSlackWebhook, + respx_mock: respx.Router, + jupyter: MockJupyter, ) -> None: mock_gafaelfawr(respx_mock) diff --git a/tests/monkeyflocker_test.py b/tests/monkeyflocker_test.py index 4c9ad8ca..46ddf5b1 100644 --- a/tests/monkeyflocker_test.py +++ b/tests/monkeyflocker_test.py @@ -31,7 +31,9 @@ @pytest.fixture -def monkeyflocker_app(tmp_path: Path) -> Iterator[UvicornProcess]: +def monkeyflocker_app( + tmp_path: Path, test_filesystem: Path, environment_url: str +) -> Iterator[UvicornProcess]: """Run the application as a separate process for monkeyflocker access.""" assert config.environment_url config.gafaelfawr_token = make_gafaelfawr_token() diff --git a/tests/status_test.py b/tests/status_test.py index 34ba510b..ce575321 100644 --- a/tests/status_test.py +++ b/tests/status_test.py @@ -49,7 +49,7 @@ async def test_post_status( await post_status() expected = """\ -Currently running 3 flocks against https://test.example.com: +Currently running 3 flocks against https://example.com: • *notebook*: 5 monkeys started 2021-08-20 with 3 failures (99.39% success) • *tap*: 1 monkey started 2021-08-20 with 1 failure (99.99% success) • *login*: 2 monkeys (not started) with 0 failures (100.00% success) diff --git a/tests/support/jupyter.py b/tests/support/jupyter.py deleted file mode 100644 index 2a32f70a..00000000 --- a/tests/support/jupyter.py +++ /dev/null @@ -1,481 +0,0 @@ -"""A mock JupyterHub and lab for tests.""" - -from __future__ import annotations - -import asyncio -import json -import os -import re -from base64 import urlsafe_b64decode -from collections.abc import AsyncIterator -from contextlib import redirect_stdout -from dataclasses import dataclass -from datetime import datetime, timedelta -from enum import Enum -from io import StringIO -from re import Pattern -from traceback import format_exc -from typing import Any -from unittest.mock import ANY -from urllib.parse import parse_qs -from uuid import uuid4 - -import respx -from httpx import Request, Response -from safir.datetime import current_datetime - -from mobu.config import config -from mobu.services.business.nublado import _GET_IMAGE, _GET_NODE - - -class JupyterAction(Enum): - """Possible actions on the Jupyter lab state machine.""" - - LOGIN = "login" - HOME = "home" - HUB = "hub" - USER = "user" - PROGRESS = "progress" - SPAWN = "spawn" - SPAWN_PENDING = "spawn_pending" - LAB = "lab" - DELETE_LAB = "delete_lab" - CREATE_SESSION = "create_session" - DELETE_SESSION = "delete_session" - - -@dataclass -class JupyterLabSession: - """Metadata for an open Jupyter lab session.""" - - session_id: str - kernel_id: str - - -class JupyterState(Enum): - """Possible states the Jupyter lab can be in.""" - - LOGGED_OUT = "logged out" - LOGGED_IN = "logged in" - SPAWN_PENDING = "spawn pending" - LAB_RUNNING = "lab running" - - -def _url(route: str) -> str: - """Construct a URL for JupyterHub or its proxy.""" - assert config.environment_url - base_url = str(config.environment_url).rstrip("/") - return f"{base_url}/nb/{route}" - - -def _url_regex(route: str) -> Pattern[str]: - """Construct a regex matching a URL for JupyterHub or its proxy.""" - assert config.environment_url - base_url = str(config.environment_url).rstrip("/") - return re.compile(re.escape(f"{base_url}/nb/") + route) - - -class MockJupyter: - """A mock Jupyter state machine. - - This should be invoked via mocked HTTP calls so that tests can simulate - making REST calls to the real JupyterHub and lab. It simulates the process - of spawning a lab, creating a session, and running code within that - session. - """ - - def __init__(self) -> None: - self.sessions: dict[str, JupyterLabSession] = {} - self.state: dict[str, JupyterState] = {} - self.delete_immediate = True - self.spawn_timeout = False - self.redirect_loop = False - self.lab_form: dict[str, dict[str, str]] = {} - self.expected_session_name = "(no notebook)" - self.expected_session_type = "console" - self._delete_at: dict[str, datetime | None] = {} - self._fail: dict[str, dict[JupyterAction, bool]] = {} - self._hub_xsrf = os.urandom(8).hex() - self._lab_xsrf = os.urandom(8).hex() - - @staticmethod - def get_user(authorization: str) -> str: - """Get the user from the Authorization header.""" - assert authorization.startswith("Bearer ") - token = authorization.split(" ", 1)[1] - user = urlsafe_b64decode(token[3:].split(".", 1)[0].encode()) - return user.decode() - - def fail(self, user: str, action: JupyterAction) -> None: - """Configure the given action to fail for the given user.""" - if user not in self._fail: - self._fail[user] = {} - self._fail[user][action] = True - - def login(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - if JupyterAction.LOGIN in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - if state == JupyterState.LOGGED_OUT: - self.state[user] = JupyterState.LOGGED_IN - xsrf = f"_xsrf={self._hub_xsrf}" - return Response(200, request=request, headers={"Set-Cookie": xsrf}) - - def user(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - if JupyterAction.USER in self._fail.get(user, {}): - return Response(500, request=request) - assert str(request.url).endswith(f"/hub/api/users/{user}") - assert request.headers.get("x-xsrftoken") == self._hub_xsrf - state = self.state.get(user, JupyterState.LOGGED_OUT) - if state == JupyterState.SPAWN_PENDING: - server = {"name": "", "pending": "spawn", "ready": False} - body = {"name": user, "servers": {"": server}} - elif state == JupyterState.LAB_RUNNING: - delete_at = self._delete_at.get(user) - if delete_at and current_datetime(microseconds=True) > delete_at: - del self._delete_at[user] - self.state[user] = JupyterState.LOGGED_IN - if delete_at: - server = {"name": "", "pending": "delete", "ready": False} - else: - server = {"name": "", "pending": None, "ready": True} - body = {"name": user, "servers": {"": server}} - else: - body = {"name": user, "servers": {}} - return Response(200, json=body, request=request) - - async def progress(self, request: Request) -> Response: - if self.redirect_loop: - return Response( - 303, headers={"Location": str(request.url)}, request=request - ) - user = self.get_user(request.headers["Authorization"]) - expected_suffix = f"/hub/api/users/{user}/server/progress" - assert str(request.url).endswith(expected_suffix) - assert request.headers.get("x-xsrftoken") == self._hub_xsrf - state = self.state.get(user, JupyterState.LOGGED_OUT) - assert state in (JupyterState.SPAWN_PENDING, JupyterState.LAB_RUNNING) - if JupyterAction.PROGRESS in self._fail.get(user, {}): - body = ( - 'data: {"progress": 0, "message": "Server requested"}\n\n' - 'data: {"progress": 50, "message": "Spawning server..."}\n\n' - 'data: {"progress": 75, "message": "Spawn failed!"}\n\n' - ) - elif state == JupyterState.LAB_RUNNING: - body = ( - 'data: {"progress": 100, "ready": true, "message": "Ready"}\n' - "\n" - ) - elif self.spawn_timeout: - # Cause the spawn to time out by pausing for longer than the test - # should run for and then returning nothing. - await asyncio.sleep(60) - body = "" - else: - self.state[user] = JupyterState.LAB_RUNNING - body = ( - 'data: {"progress": 0, "message": "Server requested"}\n\n' - 'data: {"progress": 50, "message": "Spawning server..."}\n\n' - 'data: {"progress": 100, "ready": true, "message": "Ready"}\n' - "\n" - ) - return Response( - 200, - text=body, - headers={"Content-Type": "text/event-stream"}, - request=request, - ) - - def spawn(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - if JupyterAction.SPAWN in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - assert state == JupyterState.LOGGED_IN - assert request.headers.get("x-xsrftoken") == self._hub_xsrf - self.state[user] = JupyterState.SPAWN_PENDING - self.lab_form[user] = { - k: v[0] for k, v in parse_qs(request.content.decode()).items() - } - url = _url(f"hub/spawn-pending/{user}") - return Response(302, headers={"Location": url}, request=request) - - def spawn_pending(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - assert str(request.url).endswith(f"/hub/spawn-pending/{user}") - if JupyterAction.SPAWN_PENDING in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - assert state == JupyterState.SPAWN_PENDING - assert request.headers.get("x-xsrftoken") == self._hub_xsrf - return Response(200, request=request) - - def missing_lab(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - assert str(request.url).endswith(f"/hub/user/{user}/lab") - return Response(503, request=request) - - def lab(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - assert str(request.url).endswith(f"/user/{user}/lab") - if JupyterAction.LAB in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - if state == JupyterState.LAB_RUNNING: - # In real life, there's another redirect to - # /hub/api/oauth2/authorize, which doesn't set a cookie, - # and then redirects to /user/username/oauth_callback. - # - # We're skipping that one since it doesn't change the - # client state at all. - xsrf = f"_xsrf={self._lab_xsrf}" - return Response( - 302, - request=request, - headers={ - "Location": _url(f"user/{user}/oauth_callback"), - "Set-Cookie": xsrf, - }, - ) - else: - return Response( - 302, - headers={"Location": _url(f"hub/user/{user}/lab")}, - request=request, - ) - - def lab_callback(self, request: Request) -> Response: - """Simulate not setting the ``_xsrf`` cookie on first request. - - This happens at the end of a chain from ``/user/username/lab`` to - ``/hub/api/oauth2/authorize``, which then issues a redirect to - ``/user/username/oauth_callback``. It is in the final redirect - that the ``_xsrf`` cookie is actually set, and then it returns - a 200. - """ - user = self.get_user(request.headers["Authorization"]) - assert str(request.url).endswith(f"/user/{user}/oauth_callback") - return Response(200, request=request) - - def delete_lab(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - assert str(request.url).endswith(f"/users/{user}/server") - assert request.headers.get("x-xsrftoken") == self._hub_xsrf - if JupyterAction.DELETE_LAB in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - assert state != JupyterState.LOGGED_OUT - if self.delete_immediate: - self.state[user] = JupyterState.LOGGED_IN - else: - now = current_datetime(microseconds=True) - self._delete_at[user] = now + timedelta(seconds=5) - return Response(202, request=request) - - def create_session(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - assert str(request.url).endswith(f"/user/{user}/api/sessions") - assert request.headers.get("x-xsrftoken") == self._lab_xsrf - assert user not in self.sessions - if JupyterAction.CREATE_SESSION in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - assert state == JupyterState.LAB_RUNNING - body = json.loads(request.content.decode()) - assert body["kernel"]["name"] == "LSST" - assert body["name"] == self.expected_session_name - assert body["type"] == self.expected_session_type - session = JupyterLabSession( - session_id=uuid4().hex, kernel_id=uuid4().hex - ) - self.sessions[user] = session - return Response( - 201, - json={ - "id": session.session_id, - "kernel": {"id": session.kernel_id}, - }, - request=request, - ) - - def delete_session(self, request: Request) -> Response: - user = self.get_user(request.headers["Authorization"]) - session_id = self.sessions[user].session_id - expected_suffix = f"/user/{user}/api/sessions/{session_id}" - assert str(request.url).endswith(expected_suffix) - assert request.headers.get("x-xsrftoken") == self._lab_xsrf - if JupyterAction.DELETE_SESSION in self._fail.get(user, {}): - return Response(500, request=request) - state = self.state.get(user, JupyterState.LOGGED_OUT) - assert state == JupyterState.LAB_RUNNING - del self.sessions[user] - return Response(204, request=request) - - -class MockJupyterWebSocket: - """Simulate the WebSocket connection to a Jupyter Lab. - - Note - ---- - The methods are named the reverse of what you would expect: ``send`` - receives a message, and ``recv`` sends a message back to the caller. This - is because this is a mock of a client library but is simulating a server, - so is operating in the reverse direction. - """ - - def __init__(self, user: str, session_id: str) -> None: - self.user = user - self.session_id = session_id - self._header: dict[str, str] | None = None - self._code: str | None = None - self._state: dict[str, Any] = {} - - async def close(self) -> None: - pass - - async def send(self, message_str: str) -> None: - message = json.loads(message_str) - assert message == { - "header": { - "username": self.user, - "version": "5.0", - "session": self.session_id, - "msg_id": ANY, - "msg_type": "execute_request", - }, - "parent_header": {}, - "channel": "shell", - "content": { - "code": ANY, - "silent": False, - "store_history": False, - "user_expressions": {}, - "allow_stdin": False, - }, - "metadata": {}, - "buffers": {}, - } - self._header = message["header"] - self._code = message["content"]["code"] - - async def __aiter__(self) -> AsyncIterator[str]: - while True: - assert self._header - response = self._build_response() - yield json.dumps(response) - - def _build_response(self) -> dict[str, Any]: - if self._code == _GET_IMAGE: - self._code = None - return { - "msg_type": "stream", - "parent_header": self._header, - "content": { - "text": ( - "lighthouse.ceres/library/sketchbook:recommended\n" - "Recommended (Weekly 2077_43)\n" - ) - }, - } - elif self._code == _GET_NODE: - self._code = None - return { - "msg_type": "stream", - "parent_header": self._header, - "content": {"text": "some-node"}, - } - elif self._code == "long_error_for_test()": - error = "" - line = "this is a single line of output to test trimming errors" - for i in range(int(3000 / len(line))): - error += f"{line} #{i}\n" - self._code = None - return { - "msg_type": "error", - "parent_header": self._header, - "content": {"traceback": error}, - } - elif self._code: - try: - output = StringIO() - with redirect_stdout(output): - exec(self._code, self._state) # noqa: S102 - self._code = None - return { - "msg_type": "stream", - "parent_header": self._header, - "content": {"text": output.getvalue()}, - } - except Exception: - result = { - "msg_type": "error", - "parent_header": self._header, - "content": {"traceback": format_exc()}, - } - self._header = None - return result - else: - result = { - "msg_type": "execute_reply", - "parent_header": self._header, - "content": {"status": "ok"}, - } - self._header = None - return result - - -def mock_jupyter(respx_mock: respx.Router) -> MockJupyter: - """Set up a mock JupyterHub and lab.""" - mock = MockJupyter() - respx_mock.get(_url("hub/home")).mock(side_effect=mock.login) - respx_mock.get(_url("hub/spawn")).mock(return_value=Response(200)) - respx_mock.post(_url("hub/spawn")).mock(side_effect=mock.spawn) - regex = _url_regex("hub/spawn-pending/[^/]+$") - respx_mock.get(url__regex=regex).mock(side_effect=mock.spawn_pending) - regex = _url_regex("hub/user/[^/]+/lab$") - respx_mock.get(url__regex=regex).mock(side_effect=mock.missing_lab) - regex = _url_regex("hub/api/users/[^/]+$") - respx_mock.get(url__regex=regex).mock(side_effect=mock.user) - regex = _url_regex("hub/api/users/[^/]+/server/progress$") - respx_mock.get(url__regex=regex).mock(side_effect=mock.progress) - regex = _url_regex("hub/api/users/[^/]+/server") - respx_mock.delete(url__regex=regex).mock(side_effect=mock.delete_lab) - regex = _url_regex(r"user/[^/]+/lab") - respx_mock.get(url__regex=regex).mock(side_effect=mock.lab) - regex = _url_regex(r"user/[^/]+/oauth_callback") - respx_mock.get(url__regex=regex).mock(side_effect=mock.lab_callback) - regex = _url_regex("user/[^/]+/api/sessions") - respx_mock.post(url__regex=regex).mock(side_effect=mock.create_session) - regex = _url_regex("user/[^/]+/api/sessions/[^/]+$") - respx_mock.delete(url__regex=regex).mock(side_effect=mock.delete_session) - return mock - - -def mock_jupyter_websocket( - url: str, headers: dict[str, str], jupyter: MockJupyter -) -> MockJupyterWebSocket: - """Create a new mock ClientWebSocketResponse that simulates a lab. - - Parameters - ---------- - url - URL of the request to open a WebSocket. - headers - Extra headers sent with that request. - jupyter - Mock JupyterHub. - - Returns - ------- - MockJupyterWebSocket - Mock WebSocket connection. - """ - match = re.search("/user/([^/]+)/api/kernels/([^/]+)/channels", url) - assert match - user = match.group(1) - assert user == jupyter.get_user(headers["authorization"]) - session = jupyter.sessions[user] - assert match.group(2) == session.kernel_id - return MockJupyterWebSocket(user, session.session_id) diff --git a/tests/support/monkeyflocker.py b/tests/support/monkeyflocker.py index 0c79a5ca..753a1aeb 100644 --- a/tests/support/monkeyflocker.py +++ b/tests/support/monkeyflocker.py @@ -3,15 +3,17 @@ from __future__ import annotations from collections.abc import Awaitable, Callable +from pathlib import Path import respx from fastapi import FastAPI, Request, Response +from rubin.nublado.client.testing import mock_jupyter from starlette.middleware.base import BaseHTTPMiddleware from mobu.main import app +from .constants import TEST_BASE_URL from .gafaelfawr import mock_gafaelfawr -from .jupyter import mock_jupyter class AddAuthHeaderMiddleware(BaseHTTPMiddleware): @@ -36,9 +38,12 @@ async def dispatch( def create_app() -> FastAPI: - """Configure the FastAPI app for monkeyflocker testing.""" + """Configure the FastAPI app for monkeyflocker testing. + + This cannot have any arguments, so we pick arbitrary ones for mock_jupyter. + """ respx.start() mock_gafaelfawr(respx.mock) - mock_jupyter(respx.mock) + mock_jupyter(respx.mock, base_url=TEST_BASE_URL, user_dir=Path()) app.add_middleware(AddAuthHeaderMiddleware) return app