diff --git a/requirements/dev.txt b/requirements/dev.txt index ab314a8..9461d5f 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,9 +4,9 @@ # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements/dev.txt requirements/dev.in # -anyio==4.0.0 \ - --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f \ - --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a +anyio==3.7.1 \ + --hash=sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780 \ + --hash=sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5 # via # -c requirements/main.txt # httpcore diff --git a/requirements/main.in b/requirements/main.in index cd33e81..794e587 100644 --- a/requirements/main.in +++ b/requirements/main.in @@ -15,6 +15,7 @@ jupyterhub==3.1.1 httpx httpx-sse PyYAML +safir>=4.5.0 traitlets # Additional dependencies required by our configuration of JupyterHub. @@ -31,3 +32,7 @@ kubernetes_asyncio # JupyterHub image currently uses. exceptiongroup ruamel.yaml.clib + +# Uncomment this, change the branch, comment out safir above, and run make +# update-deps-no-hashes to test against an unreleased version of Safir. +# safir @ git+https://github.com/lsst-sqre/safir@main diff --git a/requirements/main.txt b/requirements/main.txt index 3e6707d..15d901d 100644 --- a/requirements/main.txt +++ b/requirements/main.txt @@ -101,10 +101,13 @@ alembic==1.12.0 \ --hash=sha256:03226222f1cf943deee6c85d9464261a6c710cd19b4fe867a3ad1f25afda610f \ --hash=sha256:8e7645c32e4f200675e69f0745415335eb59a3663f5feb487abfa0b30c45888b # via jupyterhub -anyio==4.0.0 \ - --hash=sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f \ - --hash=sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a - # via httpcore +anyio==3.7.1 \ + --hash=sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780 \ + --hash=sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5 + # via + # fastapi + # httpcore + # starlette async-generator==1.10 \ --hash=sha256:01c7bf666359b4967d2cda0000cc2e4af16a0ae098cbffcb8472fb9e8ad6585b \ --hash=sha256:6ebb3d106c12920aaae42ccb6f787ef5eefdcdd166ea3d628fa8476abe712144 @@ -277,35 +280,46 @@ charset-normalizer==3.2.0 \ # via # aiohttp # requests -cryptography==41.0.3 \ - --hash=sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306 \ - --hash=sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84 \ - --hash=sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47 \ - --hash=sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d \ - --hash=sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116 \ - --hash=sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207 \ - --hash=sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81 \ - --hash=sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087 \ - --hash=sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd \ - --hash=sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507 \ - --hash=sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858 \ - --hash=sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae \ - --hash=sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34 \ - --hash=sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906 \ - --hash=sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd \ - --hash=sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922 \ - --hash=sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7 \ - --hash=sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4 \ - --hash=sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574 \ - --hash=sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1 \ - --hash=sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c \ - --hash=sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e \ - --hash=sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de - # via pyopenssl +click==8.1.7 \ + --hash=sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28 \ + --hash=sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de + # via safir +cryptography==41.0.4 \ + --hash=sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67 \ + --hash=sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311 \ + --hash=sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8 \ + --hash=sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13 \ + --hash=sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143 \ + --hash=sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f \ + --hash=sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829 \ + --hash=sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd \ + --hash=sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397 \ + --hash=sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac \ + --hash=sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d \ + --hash=sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a \ + --hash=sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839 \ + --hash=sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e \ + --hash=sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6 \ + --hash=sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9 \ + --hash=sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860 \ + --hash=sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca \ + --hash=sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91 \ + --hash=sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d \ + --hash=sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714 \ + --hash=sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb \ + --hash=sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f + # via + # pyjwt + # pyopenssl + # safir exceptiongroup==1.1.3 \ --hash=sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9 \ --hash=sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3 # via -r requirements/main.in +fastapi==0.103.1 \ + --hash=sha256:345844e6a82062f06a096684196aaf96c1198b25c06b72c1311b882aa2d8a35d \ + --hash=sha256:5e5f17e826dbd9e9b5a5145976c5cd90bcaa61f2bf9a69aca423f2bcebe44d83 + # via safir frozenlist==1.4.0 \ --hash=sha256:007df07a6e3eb3e33e9a1fe6a9db7af152bbd8a185f9aaa6ece10a3529e3e1c6 \ --hash=sha256:008eb8b31b3ea6896da16c38c1b136cb9fec9e249e77f6211d479db79a4eaf01 \ @@ -371,6 +385,10 @@ frozenlist==1.4.0 \ # via # aiohttp # aiosignal +gidgethub==5.3.0 \ + --hash=sha256:4dd92f2252d12756b13f9dd15cde322bfb0d625b6fb5d680da1567ec74b462c0 \ + --hash=sha256:9ece7d37fbceb819b80560e7ed58f936e48a65d37ec5f56db79145156b426a25 + # via safir greenlet==2.0.2 \ --hash=sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a \ --hash=sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a \ @@ -448,7 +466,9 @@ httpcore==0.18.0 \ httpx==0.25.0 \ --hash=sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100 \ --hash=sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875 - # via -r requirements/main.in + # via + # -r requirements/main.in + # safir httpx-sse==0.3.1 \ --hash=sha256:3bb3289b2867f50cbdb2fee3eeeefecb1e86653122e164faac0023f1ffc88aea \ --hash=sha256:7376dd88732892f9b6b549ac0ad05a8e2341172fe7dcf9f8f9c8050934297316 @@ -465,9 +485,9 @@ jinja2==3.1.2 \ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 # via jupyterhub -jsonschema==4.19.0 \ - --hash=sha256:043dc26a3845ff09d20e4420d6012a9c91c9aa8999fa184e7efcfeccb41e32cb \ - --hash=sha256:6e1e7569ac13be8139b2dd2c21a55d350066ee3f80df06c608b398cdc6f30e8f +jsonschema==4.19.1 \ + --hash=sha256:cd5f1f9ed9444e554b38ba003af06c0a8c2868131e56bfbef0550fb450c0330e \ + --hash=sha256:ec84cc37cfa703ef7cd4928db24f9cb31428a5d0fa77747b8b51a847458e0bbf # via jupyter-telemetry jsonschema-specifications==2023.7.1 \ --hash=sha256:05adf340b659828a004220a9613be00fa3f223f2b82002e273dee62fd50524b1 \ @@ -668,6 +688,50 @@ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi +pydantic==1.10.12 \ + --hash=sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303 \ + --hash=sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe \ + --hash=sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47 \ + --hash=sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494 \ + --hash=sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33 \ + --hash=sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86 \ + --hash=sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d \ + --hash=sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c \ + --hash=sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a \ + --hash=sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565 \ + --hash=sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb \ + --hash=sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62 \ + --hash=sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62 \ + --hash=sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0 \ + --hash=sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523 \ + --hash=sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d \ + --hash=sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405 \ + --hash=sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f \ + --hash=sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b \ + --hash=sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718 \ + --hash=sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed \ + --hash=sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb \ + --hash=sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5 \ + --hash=sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc \ + --hash=sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942 \ + --hash=sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe \ + --hash=sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246 \ + --hash=sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350 \ + --hash=sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303 \ + --hash=sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09 \ + --hash=sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33 \ + --hash=sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8 \ + --hash=sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a \ + --hash=sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1 \ + --hash=sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6 \ + --hash=sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d + # via + # fastapi + # safir +pyjwt[crypto]==2.8.0 \ + --hash=sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de \ + --hash=sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320 + # via gidgethub pyopenssl==23.2.0 \ --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac @@ -893,6 +957,10 @@ ruamel-yaml-clib==0.2.7 \ # via # -r requirements/main.in # ruamel-yaml +safir==4.5.0 \ + --hash=sha256:19268a22f9e530a98a780e416e4a8c79b40e275853fdae031d15f8f99fe7ebf4 \ + --hash=sha256:36301d094f4da08f1f54a3c7379db6603fa04e383df27a84a54c8a0a7a6cdf6e + # via -r requirements/main.in six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 @@ -906,51 +974,61 @@ sniffio==1.3.0 \ # anyio # httpcore # httpx -sqlalchemy==2.0.20 \ - --hash=sha256:1506e988ebeaaf316f183da601f24eedd7452e163010ea63dbe52dc91c7fc70e \ - --hash=sha256:1a58052b5a93425f656675673ef1f7e005a3b72e3f2c91b8acca1b27ccadf5f4 \ - --hash=sha256:1b74eeafaa11372627ce94e4dc88a6751b2b4d263015b3523e2b1e57291102f0 \ - --hash=sha256:1be86ccea0c965a1e8cd6ccf6884b924c319fcc85765f16c69f1ae7148eba64b \ - --hash=sha256:1d35d49a972649b5080557c603110620a86aa11db350d7a7cb0f0a3f611948a0 \ - --hash=sha256:243d0fb261f80a26774829bc2cee71df3222587ac789b7eaf6555c5b15651eed \ - --hash=sha256:26a3399eaf65e9ab2690c07bd5cf898b639e76903e0abad096cd609233ce5208 \ - --hash=sha256:27d554ef5d12501898d88d255c54eef8414576f34672e02fe96d75908993cf53 \ - --hash=sha256:3364b7066b3c7f4437dd345d47271f1251e0cfb0aba67e785343cdbdb0fff08c \ - --hash=sha256:3423dc2a3b94125094897118b52bdf4d37daf142cbcf26d48af284b763ab90e9 \ - --hash=sha256:3c6aceebbc47db04f2d779db03afeaa2c73ea3f8dcd3987eb9efdb987ffa09a3 \ - --hash=sha256:3ce5e81b800a8afc870bb8e0a275d81957e16f8c4b62415a7b386f29a0cb9763 \ - --hash=sha256:411e7f140200c02c4b953b3dbd08351c9f9818d2bd591b56d0fa0716bd014f1e \ - --hash=sha256:4cde2e1096cbb3e62002efdb7050113aa5f01718035ba9f29f9d89c3758e7e4e \ - --hash=sha256:5768c268df78bacbde166b48be788b83dddaa2a5974b8810af422ddfe68a9bc8 \ - --hash=sha256:599ccd23a7146e126be1c7632d1d47847fa9f333104d03325c4e15440fc7d927 \ - --hash=sha256:5ed61e3463021763b853628aef8bc5d469fe12d95f82c74ef605049d810f3267 \ - --hash=sha256:63a368231c53c93e2b67d0c5556a9836fdcd383f7e3026a39602aad775b14acf \ - --hash=sha256:63e73da7fb030ae0a46a9ffbeef7e892f5def4baf8064786d040d45c1d6d1dc5 \ - --hash=sha256:6eb6d77c31e1bf4268b4d61b549c341cbff9842f8e115ba6904249c20cb78a61 \ - --hash=sha256:6f8a934f9dfdf762c844e5164046a9cea25fabbc9ec865c023fe7f300f11ca4a \ - --hash=sha256:6fe7d61dc71119e21ddb0094ee994418c12f68c61b3d263ebaae50ea8399c4d4 \ - --hash=sha256:759b51346aa388c2e606ee206c0bc6f15a5299f6174d1e10cadbe4530d3c7a98 \ - --hash=sha256:76fdfc0f6f5341987474ff48e7a66c3cd2b8a71ddda01fa82fedb180b961630a \ - --hash=sha256:77d37c1b4e64c926fa3de23e8244b964aab92963d0f74d98cbc0783a9e04f501 \ - --hash=sha256:79543f945be7a5ada9943d555cf9b1531cfea49241809dd1183701f94a748624 \ - --hash=sha256:79fde625a0a55220d3624e64101ed68a059c1c1f126c74f08a42097a72ff66a9 \ - --hash=sha256:7d3f175410a6db0ad96b10bfbb0a5530ecd4fcf1e2b5d83d968dd64791f810ed \ - --hash=sha256:8dd77fd6648b677d7742d2c3cc105a66e2681cc5e5fb247b88c7a7b78351cf74 \ - --hash=sha256:a3f0dd6d15b6dc8b28a838a5c48ced7455c3e1fb47b89da9c79cc2090b072a50 \ - --hash=sha256:bcb04441f370cbe6e37c2b8d79e4af9e4789f626c595899d94abebe8b38f9a4d \ - --hash=sha256:c3d99ba99007dab8233f635c32b5cd24fb1df8d64e17bc7df136cedbea427897 \ - --hash=sha256:ca8a5ff2aa7f3ade6c498aaafce25b1eaeabe4e42b73e25519183e4566a16fc6 \ - --hash=sha256:cb0d3e94c2a84215532d9bcf10229476ffd3b08f481c53754113b794afb62d14 \ - --hash=sha256:d1b09ba72e4e6d341bb5bdd3564f1cea6095d4c3632e45dc69375a1dbe4e26ec \ - --hash=sha256:d32b5ffef6c5bcb452723a496bad2d4c52b346240c59b3e6dba279f6dcc06c14 \ - --hash=sha256:d3793dcf5bc4d74ae1e9db15121250c2da476e1af8e45a1d9a52b1513a393459 \ - --hash=sha256:dd81466bdbc82b060c3c110b2937ab65ace41dfa7b18681fdfad2f37f27acdd7 \ - --hash=sha256:e4e571af672e1bb710b3cc1a9794b55bce1eae5aed41a608c0401885e3491179 \ - --hash=sha256:ea8186be85da6587456c9ddc7bf480ebad1a0e6dcbad3967c4821233a4d4df57 \ - --hash=sha256:eefebcc5c555803065128401a1e224a64607259b5eb907021bf9b175f315d2a6 +sqlalchemy==2.0.21 \ + --hash=sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e \ + --hash=sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6 \ + --hash=sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258 \ + --hash=sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce \ + --hash=sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede \ + --hash=sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce \ + --hash=sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4 \ + --hash=sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4 \ + --hash=sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5 \ + --hash=sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01 \ + --hash=sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9 \ + --hash=sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9 \ + --hash=sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67 \ + --hash=sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9 \ + --hash=sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9 \ + --hash=sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301 \ + --hash=sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8 \ + --hash=sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615 \ + --hash=sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa \ + --hash=sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4 \ + --hash=sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446 \ + --hash=sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4 \ + --hash=sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178 \ + --hash=sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af \ + --hash=sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b \ + --hash=sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe \ + --hash=sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9 \ + --hash=sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09 \ + --hash=sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a \ + --hash=sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063 \ + --hash=sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef \ + --hash=sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1 \ + --hash=sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66 \ + --hash=sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231 \ + --hash=sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e \ + --hash=sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec \ + --hash=sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430 \ + --hash=sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce \ + --hash=sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9 \ + --hash=sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa \ + --hash=sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880 # via # alembic # jupyterhub +starlette==0.27.0 \ + --hash=sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75 \ + --hash=sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91 + # via + # fastapi + # safir +structlog==23.1.0 \ + --hash=sha256:270d681dd7d163c11ba500bc914b2472d2b50a8ef00faa999ded5ff83a2f906b \ + --hash=sha256:79b9e68e48b54e373441e130fa447944e6f87a05b35de23138e475c05d0f7e0e + # via safir tornado==6.3.3 \ --hash=sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f \ --hash=sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5 \ @@ -978,10 +1056,16 @@ typing-extensions==4.8.0 \ --hash=sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef # via # alembic + # fastapi + # pydantic # sqlalchemy -urllib3==2.0.4 \ - --hash=sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11 \ - --hash=sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4 +uritemplate==4.1.1 \ + --hash=sha256:4346edfc5c3b79f694bccd6d6099a322bbeb628dbf2cd86eea55a456ce5124f0 \ + --hash=sha256:830c08b8d99bdd312ea4ead05994a38e8936266f84b9a7878232db50b044e02e + # via gidgethub +urllib3==2.0.5 \ + --hash=sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594 \ + --hash=sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e # via # kubernetes-asyncio # requests diff --git a/src/rsp_restspawner/spawner.py b/src/rsp_restspawner/spawner.py index d0c8abd..e99cf12 100644 --- a/src/rsp_restspawner/spawner.py +++ b/src/rsp_restspawner/spawner.py @@ -14,6 +14,7 @@ from httpx import AsyncClient, HTTPError, Response from httpx_sse import ServerSentEvent, aconnect_sse from jupyterhub.spawner import Spawner +from safir.asyncio import AsyncMultiQueue from traitlets import Unicode, default from .exceptions import ( @@ -186,11 +187,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) # Holds the events from a spawn in progress. - self._events: list[SpawnEvent] = [] - - # Triggers used to notify listeners of new events. Each listener gets - # its own trigger. - self._triggers: list[asyncio.Event] = [] + self._events = AsyncMultiQueue[SpawnEvent]() # Holds the future representing a spawn in progress, used by the # progress method to know when th spawn is finished and it should @@ -320,7 +317,7 @@ async def progress(self) -> AsyncIterator[dict[str, int | str]]: Yields ------ - dict of str to str or int + dict of str or int Dictionary representing the event with fields ``progress``, containing an integer completion percentage, and ``message``, containing a human-readable description of the event. @@ -335,53 +332,21 @@ async def progress(self) -> AsyncIterator[dict[str, int | str]]: Uses the internal ``_start_future`` attribute to track when the related `start` method has completed. """ - next_event = 0 - complete = False - - # Insert a trigger into the trigger list that will be notified by the - # in-progress spawn. - trigger = asyncio.Event() - self._triggers.append(trigger) - - # Capture the current future and event stream in a local variable so - # that we consistently monitor the same invocation of start. If that - # one aborts and someone kicks off another one, we want to keep - # following the first one until it completes, not switch streams to - # the second one. - start_future = self._start_future - events = self._events - # We were apparently called before start was called, so there's # nothing to report. - if not start_future: + if not self._start_future: return - while not complete: - trigger.clear() - if start_future.done(): - # Indicate that we're done, but continue to execute the rest - # of the loop. We want to process any events received before - # the spawner finishes and report them before ending the - # stream. - complete = True - - # This logic tries to ensure that we don't repeat events even - # though start will be adding more events while we're working. - len_events = len(events) - for i in range(next_event, len_events): - yield events[i].to_dict() - next_event = len_events - - # Wait until we're notified that there are new events or we time - # out on the spawn. This is not the correct timeout (start_timeout - # is a bound on the total time, not each event). It's just an - # arbitrary timeout to ensure we don't wait forever, which is - # guaranteed to be longer than a spawn can take. - if not complete: - try: - await asyncio.wait_for(trigger.wait(), self.start_timeout) - except TimeoutError: - complete = True + # Iterate over the spawn events until complete. start_timeout is + # really enforced on the whole spawn process, but it's guaranteed to + # be longer than a spawn can take, so it ensures we don't wait + # forever if something goes wrong. + timeout = timedelta(seconds=self.start_timeout) + try: + async for event in self._events.aiter_from(0, timeout): + yield event.to_dict() + except TimeoutError: + pass def start(self) -> asyncio.Task[str]: """Start the user's pod. @@ -459,14 +424,7 @@ async def _start(self) -> str: to not be running at the same time, so we ignore that possibility. """ progress = 0 - - # Clear the event list (by replacing the previous list so that any - # running progress calls see the old list, not the new one), and - # notify any existing triggers and then clear the trigger list. - self._events = [] - for trigger in self._triggers: - trigger.set() - self._triggers = [] + self._events.clear() # Ask the Nublado lab controller to do the spawn and monitor its # progress until complete. @@ -484,7 +442,7 @@ async def _start(self) -> str: message="Deleting existing orphaned lab", severity="warning", ) - self._events.append(event) + self._events.put(event) await self.stop() r = await self._create_lab() @@ -501,7 +459,7 @@ async def _start(self) -> str: event = SpawnEvent.from_sse(sse, progress) if event.progress: progress = event.progress - self._events.append(event) + self._events.put(event) if event.complete: break if event.failed: @@ -511,10 +469,9 @@ async def _start(self) -> str: return await self._get_internal_url() finally: - # Ensure that we set all the triggers just before we exit so that - # none of the progress calls will get stranded waiting for a lock. - for trigger in self._triggers: - trigger.set() + # Mark the event queue as complete, which will cause all watchers + # to reach the ends of their iterators. + self._events.close() @_convert_exception async def stop(self) -> None: diff --git a/tests/spawner_test.py b/tests/spawner_test.py index c28fcea..a49e866 100644 --- a/tests/spawner_test.py +++ b/tests/spawner_test.py @@ -78,7 +78,7 @@ async def test_options_form(spawner: RSPRestSpawner) -> None: @pytest.mark.asyncio async def test_progress(spawner: RSPRestSpawner) -> None: - await spawner.start() + results = await asyncio.gather(spawner.start(), collect_progress(spawner)) user = spawner.user.name expected = [ {"progress": 2, "message": "[info] Lab creation initiated"}, @@ -88,11 +88,11 @@ async def test_progress(spawner: RSPRestSpawner) -> None: "message": f"[info] Pod successfully spawned for {user}", }, ] - index = 0 - async for message in spawner.progress(): - assert message == expected[index] - index += 1 - assert index == len(expected) + assert results[1] == expected + + # A new reader should be able to come along after the lab has spawned and + # see the same progress messages with no waiting. + assert await collect_progress(spawner) == expected @pytest.mark.asyncio @@ -100,7 +100,7 @@ async def test_progress_conflict(spawner: RSPRestSpawner) -> None: await spawner.start() # Start it a second time, which should trigger deleting the old lab. - await spawner.start() + results = await asyncio.gather(spawner.start(), collect_progress(spawner)) user = spawner.user.name expected = [ {"progress": 1, "message": "[warning] Deleting existing orphaned lab"}, @@ -111,11 +111,7 @@ async def test_progress_conflict(spawner: RSPRestSpawner) -> None: "message": f"[info] Pod successfully spawned for {user}", }, ] - index = 0 - async for message in spawner.progress(): - assert message == expected[index] - index += 1 - assert index == len(expected) + assert results[1] == expected @pytest.mark.asyncio