diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..943c015 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,40 @@ +name: Deploy Documentation to GitHub Pages + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.9' + + - name: Install dependencies + env: + PIPENV_VENV_IN_PROJECT: true + run: | + python -m pip install --upgrade pip + pip install pipenv + pipenv install -d + sudo apt-get update && sudo apt-get install -y pandoc + + - name: Build documentation + run: | + pandoc -s readme.md -o docs/readme.rst + pipenv run sphinx-build -M html docs/ docs/_build + + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/_build/html \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6bb8e1c..27a0e42 100644 --- a/.gitignore +++ b/.gitignore @@ -5,10 +5,12 @@ dev/ *.err .vscode/ __pycache__/ -*.txt build/ *.egg-info *.bin *.fzz dist/ -*.sh \ No newline at end of file +*.sh +.clang-format +docs/_build/* +docs/readme.rst \ No newline at end of file diff --git a/Pipfile b/Pipfile index 0409218..1a180d1 100644 --- a/Pipfile +++ b/Pipfile @@ -16,13 +16,18 @@ nest-asyncio = "*" aiofiles = "*" pybela = {editable = true, path = "."} paramiko = "*" +numpy = "==1.26" [dev-packages] -build = "*" twine = "*" +pip-chill = "*" +sphinx = "*" +sphinx-rtd-theme = "*" +build = "*" [scripts] test = "python test/test.py" +test-send = "python test/test-send.py" [requires] -python_version = "3.10" +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 91651f6..12c6c3c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "02b1160ef6935b4440055ecd931a1de0e774c6eec6ada4057526241b1f6f8515" + "sha256": "642dced66ad7678ff331766e41cb1c33d791a67b2d4949bb99ddf003ec615cdf" }, "pipfile-spec": 6, "requires": { - "python_version": "3.10" + "python_version": "3.9" }, "sources": [ { @@ -18,28 +18,27 @@ "default": { "aiofiles": { "hashes": [ - "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107", - "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a" + "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", + "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==23.2.1" + "version": "==24.1.0" }, "anyio": { "hashes": [ - "sha256:cfdb2b588b9fc25ede96d8db56ed50848b0b649dca3dd1df0b11f683bb9e0b5f", - "sha256:f7ed51751b2c2add651e5747c891b47e26d2a21be5d32d9311dfe9692f3e5d7a" + "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94", + "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7" ], "markers": "python_version >= '3.8'", - "version": "==4.0.0" + "version": "==4.4.0" }, "appnope": { "hashes": [ - "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", - "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" + "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", + "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c" ], "markers": "platform_system == 'Darwin'", - "version": "==0.1.3" + "version": "==0.1.4" }, "argon2-cffi": { "hashes": [ @@ -101,182 +100,188 @@ }, "attrs": { "hashes": [ - "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04", - "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.1.0" + "version": "==24.2.0" }, "babel": { "hashes": [ - "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900", - "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed" + "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", + "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" ], - "markers": "python_version >= '3.7'", - "version": "==2.13.1" + "markers": "python_version >= '3.8'", + "version": "==2.16.0" }, "bcrypt": { "hashes": [ - "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535", - "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0", - "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410", - "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd", - "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665", - "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab", - "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71", - "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215", - "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b", - "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda", - "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9", - "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a", - "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344", - "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f", - "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d", - "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c", - "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c", - "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2", - "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d", - "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e", - "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3" + "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb", + "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399", + "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291", + "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d", + "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7", + "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170", + "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d", + "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe", + "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060", + "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184", + "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a", + "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68", + "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c", + "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458", + "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9", + "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328", + "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7", + "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34", + "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e", + "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2", + "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5", + "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae", + "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00", + "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841", + "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8", + "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221", + "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db" ], - "markers": "python_version >= '3.6'", - "version": "==4.0.1" + "markers": "python_version >= '3.7'", + "version": "==4.2.0" }, "beautifulsoup4": { "hashes": [ - "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da", - "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a" + "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051", + "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed" ], "markers": "python_full_version >= '3.6.0'", - "version": "==4.12.2" + "version": "==4.12.3" }, "bitarray": { "hashes": [ - "sha256:015908355354d42973ad41ba4eca697b4b55690b3ece6d9629118273e7a9e380", - "sha256:04fcb292637012a1551e55c00796e31b5c66d1692ca25a5ac83d23779c23cd29", - "sha256:06770f6f7d238c2e2d251e9f5346358653ea8f3dbbedc83d18598f6c044f16b4", - "sha256:07ed46857ed73765f2316e08f2d5108b7e694b44f4293e30fb526f3123c829d4", - "sha256:089a4658706ec63293c153ffb1472cea1bbefb39ccfb214f52f0c1f5d10bf28e", - "sha256:09244fa4e39ca263820dd8eca83a0175a98fb8f9bd353b4285a9ef2928b7fb41", - "sha256:095923f084d2271f28d7430798e698f6d0b304c58b072b4f2eb0bc132321323b", - "sha256:09c140daa13d2515609d5a2dbfd289eada200e96222671194dc72eae89bc3c7b", - "sha256:0c3de6517df7bbac18632046e722ca9000a4aeb76da68e545437fee1e61e2bbc", - "sha256:117a6f409dabc15320f3212d05d878cc33436c1e118e8746bf3775da2509bb7d", - "sha256:12035756896d71e82edf6a6fb46d3ca299eadbec25140c12505d4b32f561b0da", - "sha256:123333df4b22f12f4fc13fa4821b8ca075df59161bd41f5f189ffc791aaac10b", - "sha256:136bd205384a3089bc22c02a365a152e61b1e8d06ec664185c90e3ab8967260c", - "sha256:154082c814e4007bf15d8dfc576ebd4e79e9ed3626017cd53810961cee7e65d8", - "sha256:18707458f6467072a9c3322835a299fa86df8fb3962f51afac2b50c6a4babf82", - "sha256:1f142476b3bb80f6887b5a3a08d69bbd526093aee5a00973c26458cc16dd5e47", - "sha256:20cc6573ac21627e0fde854d4e0450d4c97706213bac986c0d38d252452da155", - "sha256:2448d8f5ce6d8a840a5dff1b41f5124445141530724af7ba82ec7967eabd290a", - "sha256:252bdf94c74192b10f7fdb42683adf1403892acdce39e3e3524e8b070793b1c7", - "sha256:2adb2ba1e7196f62587f4011b213b3609a717f92698a398904192e201ec3e29e", - "sha256:2aeae0f2dacf546256f8720a1e8233b6735a3bf76778be701a1736d26fe4ecec", - "sha256:2bfd32ce49d23584333087262fb367b371c74cf531f6b0c16759d59f47c847d7", - "sha256:2fcaf220e53518762dae0701082cb70d620656eaaecf5512695a6afafa885ea6", - "sha256:34ceedbeed9aefde10c273d44801971db8f7505f80933fbb936969ee2343b8a3", - "sha256:36f9752b654e18f99130a2bf84f54b1e6b8fad4f5f768f4390eb9b769a64a59c", - "sha256:38233e5793e107575be656908419d2bceab359c78c28affc386c7b88b8882b8f", - "sha256:38e19756480bff2703155060d1849d37138a1d2242287563de112fb5bdd3217d", - "sha256:3aa1bd71236e07f0e7ab859a130fc57645301fd1ffd64be9a9750bce51446acb", - "sha256:3c815a7ca72a5eebcd85caaeb4d32b71af1c795e38b3dff5dcb5b6b1f3ba0b4f", - "sha256:3d0daf70de198dcde459451c534333c0f59ab847649be013c9b88d24f0e49767", - "sha256:42d2d0123b1e68b387f4b2fd288e1a8f0dfb991cf1d2fbc56d948c3f4a113d8d", - "sha256:44e3944ebccbc38ebdb7bd3c37a9b6ff91d87db2dad4bf3910e2b01fbd36831b", - "sha256:44ee266b71cd6bd7c99f937b30ac3b7627cad04777f2c12894cd0f820cb79ada", - "sha256:47400fa421b8a3947f6676981f8d9b8581239831533dff374477ef2b86fda42f", - "sha256:48a89c2112420ebeb163a3c273c244d542cf9315c9ce5a875d305f91adcdac24", - "sha256:4a6a4e83ecab1fd1fc171c57334663b24c5d286b66421efac2428b7e105c5d62", - "sha256:4c02d24051d7070b8f3b52fa9c8984fd8eb035115545f7c4be44c9825e8b58c8", - "sha256:4d4f3e78a8c1c5bf625632488a4bdd78fe87c4603ea10443cb8f207c2a846efe", - "sha256:50923d862e01a546f942272193612f386ec1f90cc4528b10561854902bd8aab0", - "sha256:56f51107bb5406bfa4889064c01d5f9e7a545b3e2b53f159626c72c910fe8f07", - "sha256:5797552e849079ff963936a037087367f20b41d5a612b07a1ba032259a2b86c8", - "sha256:57f1fc3a089d9907859e940c6a4db3f5358013c75bba3b15156d93a58bca868e", - "sha256:5b2816afe82feeb7948e58ca0be31c254e23307953e56d3313f293f79279fbe7", - "sha256:5f35d5ff7334610b42632b30c27332b30db3680dd0174f86e382c3e150dfea2c", - "sha256:60774f73151dbcabefb5acb6d97ac09a51c999f9a903ac6f8db3d8368d338969", - "sha256:621d5658b890b99b3f8b1a678b0afed10e096d53baa767ecbcf428fce1f48415", - "sha256:63e595ca8dab2b77104e618782764bc3b172a0e9c6f97734d5fdd299063feac0", - "sha256:64d867953b530b3dde93663d4c4708b533216e9dca3f3b4489698261cd80fcef", - "sha256:67e366efaea6e0b5971593a83d062cb7e4e09e03d29f8d5b825effdf5f516ad3", - "sha256:67ee9d71af3db621aa637f96520a8df8534fcc64e881360d3ed3a07f7e47ed1b", - "sha256:6bcbe2ea34c88cf736f157cf3d713c1af112f0d7a9eec390d69a9e042b7d76d4", - "sha256:6d9ec6a214563d2edd46d1a553583782379a2cb1016e8cc6c524e011905433b1", - "sha256:72bba6b388ba7c48a882bd58c86972aab73a30c3fb5b3341f28eb5bdc17365f8", - "sha256:73fa449d9e551a063ff5c68b5d2cc0caaede5b59366d37457261ae3080f61fca", - "sha256:74efd69ac9d06ce9f43a1f513cee8a82c314f85aa0bd74664abe9e608fb59ffd", - "sha256:7554518934364b30d8da085f7a759ee3838c9ae4265b48beb82072f942b2816e", - "sha256:7618abbac8999cd942be278130b88ac6ed364ba3446222f1db0faf4de7a052cf", - "sha256:782ff781ae3c4956c15764aefc06ceb8c1c348794f09dfc8ebf62ff35166da1f", - "sha256:7a6413b5f53d44e134276d5a3747b71d17cbc25177a50445458921424a760dcd", - "sha256:7ad527ff1d398a703eba71ac270625087691e62efab8d0e331c53affe0628030", - "sha256:8011a63692e9e32cdc3fac3dfd0beceece926e8b53fb91750037fc386917f90b", - "sha256:82fe0a774204159383d1be993191d51500cb44adbd3e9287da801e4657c0d4b2", - "sha256:84a2628a5377971d73c95014e540a51327eb27ffdfbab81e43eac494eced3dc2", - "sha256:8562dd32b4d9810a0b9c04fe3d1ed8078f27d74e3738063162c677b253216666", - "sha256:879bb9f11bad60a5588f5efb4e60f42844e4787ce7d5bb0f8eb8b87a835e914f", - "sha256:8defbf10a731b44892001daa6903b2f2f7ad8c623a7b4d9ae6bd674592b1763e", - "sha256:98fe712a82f65de536b65fa9af7601df4e8231f14e3b0b14ef22e16e30d2fbea", - "sha256:993438edd54350133f7569a8691074a90aa2297def69ec0e7af34de3d175cd00", - "sha256:9a3741359cbb1a9eb50188e8faa0ced96ca658eb85061786b7f686efa94c3604", - "sha256:9b92c17b15bd5536c3e067051c67531adc81fcb6c1a699a760600ccd03dfcfba", - "sha256:9f756d159099f154a21d73932f13c8ce27f45a1c892d9b19c66a1a2c50c18474", - "sha256:a0bb2e5c0c9f964bf43a09a1cf37233ff96b3318c9a50b1b7c3d74a875b32072", - "sha256:a102cd1fafee8919a069fed9ea40c1ffe4d6037fd5b0a7f47326c2f75f24f70f", - "sha256:a2c8e06c3463746181255e03f07535c136f5346fb9c4a90eec2da27695102533", - "sha256:a4212b66f9ae2e28ca1aa0307167ebfcdb2ca263a56b786cc572699e8a717f91", - "sha256:a5e24317b0768789c52586a31284dec8ccafa2f6c128df2f2d79656142f1e794", - "sha256:a7b839e5c038111fd2fbd09e83ca945da357d690e49cfa269c09aed239db9c2b", - "sha256:a836a988ada812776af9ea6e88edf1e2eaaf38ebd545bbbcd500b2db0ced3a4f", - "sha256:a86c308018b59b999cf3d5a16889d3a347b48a2d08f34fbb4e29d5dc05fa198a", - "sha256:aa4513a7393055faef630dcfb4d10a339c47eeb943487c0e9063ba763b66cb73", - "sha256:ab7e9b1846cc62739d9d293a94f704949b588afb9ed72db00e26b7fcdb4661a3", - "sha256:ac5451951ce1e0616385e77de49afc7bd90bdf9d0aa99c0fd7b0bd23400db890", - "sha256:acf24bc6aedd0a490af71591b99401867d4445d64db09a7bfe0bde3e8498cc8d", - "sha256:adfc210df3d85017f5d2ef82db94d46b585ecbbd7357a6ee1c3bc125cc2658e2", - "sha256:b0cefac8fedb3dbbf97542dc0c6fdd8bf09a210bf6fa5799083b7309fd97b1b2", - "sha256:b153b846a6ac4b6eca71bb5f84d3dba51f3cd159f4322f5d67b2c41cf15973ad", - "sha256:b661052a4762825790a728469f897c341558392342cb68a6c54708d4e5198254", - "sha256:b8f0306dbc6605dd7f9e2dada33a3916c0c28f37128464de7153df7d8cf7a959", - "sha256:ba3f27d82b45543a7d1488d151594915a6e67fb28bd4f21eb0901df2ba4ede86", - "sha256:bbca4c4bc9854e3166474e471f3230989fd2baf32c915e363c32f91dc6ebb704", - "sha256:be7c6343a7f24293a988e5a27c1e2f44f028476e35192e73663c4acec5c4766e", - "sha256:c2ffed55994f5c73d34371474946767f936b0b83237f800be0f27a3e783baadb", - "sha256:c30dbbe2f49056d4bd97a94c07a7fc0118ecc85661fdbaada36dfa9b14dc5962", - "sha256:c3f7a6c6b78edd81fca0035fb7a156a79f25919e1b0598afb483c26513d562f1", - "sha256:c42fcddc955d84164667d899e8d4bbb763f4bc029fe72642a65df7382c46fe94", - "sha256:c65080bbba08ce07b136490b4df3d0907ec3dd76c3c5d47fda011002420f6d31", - "sha256:c87146e9c2c196c012e97273f82215e2239b9bffcbb6c7802bbbedac87be2358", - "sha256:c99838782dbec7f0c5cba1a6d4faa8e2da2b522423aa36a7f383a2265ac0ae3f", - "sha256:cadccf651900e3858e55dfd762d5de0786aec853f1fb26183905ddee233183b4", - "sha256:cb530a9fb7ed13a1a49bda81db2def4c73b7fef0fd1bb969b1d7605121869230", - "sha256:cc178297951343c8d8cd8a391999abf0024ca319671418f98dea0d7e71354126", - "sha256:d6a8a1da9205de97eea14aaa731c657fa8decd2d6878ee3d2d4bf33291960216", - "sha256:da61c6d7b6288d29db5be77048176f41f7320316997fced28b5415e1f939448e", - "sha256:de91007504b475a93d8b0949db9dec86d39c0306de9914f7b9087daeb3d9fbaf", - "sha256:e15587b2bdf18d32eb3ba25f5f5a51bedd0dc06b3112a4c53dab5e7753bc6588", - "sha256:e3d80bc6722652c847e5f503c2ce94a641b016059ec45bde4e1f13454b33e904", - "sha256:e4fd5e8a2e1b898ebc91faf6e1938bde38a4d20ee8ea49835e9adadd9b87c97c", - "sha256:e60254ac626790c8c95415b095c6831056ca57a5d31839564210530c3278f170", - "sha256:e6993e46c81702d0bb39aad83ceb228cec087bc321782fbd2c6ddff7c653dcc8", - "sha256:e76735a285e834fc9db560de11e086453128c1177950a15c3404fe16c7d76f5e", - "sha256:e98a7b510aaaf0d7368b7cb983d3106aecd28abdfa4b4593b80e7f4ab5af0a97", - "sha256:e9f4f29c0338e5862ebc3b88091d29ff28d44ab80381f238da08aabb054777c2", - "sha256:ed974048a4ced6e7b5d1cfcb83c046e70bf31b8a28eacfee3afa62f8690dee69", - "sha256:edddd6d885c7195ba7734936bc1efc8a37de18ec886a8be44a484980da87947e", - "sha256:f16a2247c27f4db3f8d01665ee97d46eaf0240b7a9feae16c17e906a3bb9a794", - "sha256:f62ee2eae65b72e034a24ac2bacd78d48845193168b54407e93bccd3772b247f", - "sha256:f69cacb3d983200114e48ec0c894e28690926f166b71202f75e976d5cd588be9", - "sha256:f8c492d90b41c510d799cc37c27892b149be77e225df6446854ce0b164e243a3", - "sha256:fbc7ac38de41052599f1e27edf4f33c02d5aea6810ee299825a81863a32e26a0", - "sha256:ff62c1c174ceae7ef0456702f9eff1f3d76590c075b9c984c459d734f73fc766", - "sha256:ff6b6b47da38223803aa3e7aab356f84e0636ecdbd43fa4bd11dbc00a923d474", - "sha256:ffa74d8601e26570f1d0e3042fda6eb26b64ba8d8dfe9b96d0bf90a6f0d81582" + "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3", + "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060", + "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9", + "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0", + "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43", + "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536", + "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d", + "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d", + "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc", + "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2", + "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981", + "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3", + "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e", + "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21", + "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13", + "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79", + "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e", + "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62", + "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034", + "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008", + "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498", + "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070", + "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d", + "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285", + "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294", + "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505", + "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7", + "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d", + "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1", + "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf", + "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47", + "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43", + "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d", + "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478", + "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae", + "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99", + "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee", + "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd", + "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3", + "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f", + "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc", + "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996", + "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333", + "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807", + "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8", + "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4", + "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8", + "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e", + "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd", + "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2", + "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9", + "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3", + "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef", + "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a", + "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1", + "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7", + "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436", + "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a", + "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce", + "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3", + "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0", + "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222", + "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0", + "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf", + "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff", + "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f", + "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15", + "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf", + "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f", + "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50", + "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e", + "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a", + "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb", + "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60", + "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659", + "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1", + "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f", + "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503", + "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7", + "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7", + "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88", + "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003", + "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd", + "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c", + "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081", + "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd", + "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96", + "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34", + "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f", + "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175", + "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed", + "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951", + "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68", + "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a", + "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a", + "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112", + "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95", + "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188", + "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b", + "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7", + "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09", + "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b", + "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7", + "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095", + "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299", + "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31", + "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf", + "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf", + "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4", + "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19", + "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff", + "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef", + "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36", + "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6", + "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3", + "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f", + "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8", + "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040", + "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd", + "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52", + "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7", + "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e" ], "index": "pypi", - "version": "==2.8.3" + "version": "==2.9.2" }, "bleach": { "hashes": [ @@ -288,78 +293,92 @@ }, "bokeh": { "hashes": [ - "sha256:2a7b3702d7e9f03ef4cd801b02b7380196c70cff2773859bcb84fa565218955c", - "sha256:783fb503d80306fb1e3c06e9c775d98675bf9e07514a776d7109178798e85683" + "sha256:b7c22fb0f7004b04f12e1b7b26ee0269a26737a08ded848fb58f6a34ec1eb155", + "sha256:c6f33817f866fc67fbeb5df79cd13a8bb592c05c591f3fd7f4f22b824f7afa01" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.3.1" + "version": "==3.4.3" }, "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2024.7.4" }, "cffi": { "hashes": [ - "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc", - "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a", - "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417", - "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab", - "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520", - "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36", - "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743", - "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8", - "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed", - "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684", - "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56", - "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324", - "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d", - "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235", - "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e", - "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088", - "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000", - "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7", - "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e", - "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673", - "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c", - "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe", - "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2", - "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098", - "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8", - "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a", - "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0", - "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b", - "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896", - "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e", - "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9", - "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2", - "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b", - "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6", - "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404", - "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f", - "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0", - "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4", - "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc", - "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936", - "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba", - "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872", - "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb", - "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614", - "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1", - "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d", - "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969", - "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b", - "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4", - "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627", - "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", - "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" - ], - "markers": "python_version >= '3.8'", - "version": "==1.16.0" + "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", + "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab", + "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499", + "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058", + "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693", + "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb", + "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", + "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", + "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2", + "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401", + "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4", + "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b", + "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", + "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f", + "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c", + "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", + "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa", + "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", + "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", + "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2", + "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", + "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e", + "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", + "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82", + "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", + "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759", + "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc", + "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", + "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf", + "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932", + "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", + "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29", + "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206", + "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2", + "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c", + "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c", + "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", + "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a", + "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", + "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", + "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", + "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", + "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", + "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", + "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", + "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", + "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", + "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", + "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", + "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42", + "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", + "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", + "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d", + "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", + "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4", + "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", + "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b", + "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", + "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e", + "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", + "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3", + "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", + "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", + "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", + "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", + "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb", + "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91" + ], + "markers": "platform_python_implementation != 'PyPy'", + "version": "==1.17.0" }, "charset-normalizer": { "hashes": [ @@ -459,114 +478,122 @@ }, "comm": { "hashes": [ - "sha256:2da8d9ebb8dd7bfc247adaff99f24dce705638a8042b85cb995066793e391001", - "sha256:a517ea2ca28931c7007a7a99c562a0fa5883cfb48963140cf642c41c948498be" + "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", + "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3" ], "markers": "python_version >= '3.8'", - "version": "==0.2.0" + "version": "==0.2.2" }, "contourpy": { "hashes": [ - "sha256:0274c1cb63625972c0c007ab14dd9ba9e199c36ae1a231ce45d725cbcbfd10a8", - "sha256:0d7e03c0f9a4f90dc18d4e77e9ef4ec7b7bbb437f7f675be8e530d65ae6ef956", - "sha256:11f8d2554e52f459918f7b8e6aa20ec2a3bce35ce95c1f0ef4ba36fbda306df5", - "sha256:139d8d2e1c1dd52d78682f505e980f592ba53c9f73bd6be102233e358b401063", - "sha256:16a7380e943a6d52472096cb7ad5264ecee36ed60888e2a3d3814991a0107286", - "sha256:171f311cb758de7da13fc53af221ae47a5877be5a0843a9fe150818c51ed276a", - "sha256:18fc2b4ed8e4a8fe849d18dce4bd3c7ea637758c6343a1f2bae1e9bd4c9f4686", - "sha256:1c203f617abc0dde5792beb586f827021069fb6d403d7f4d5c2b543d87edceb9", - "sha256:1c2559d6cffc94890b0529ea7eeecc20d6fadc1539273aa27faf503eb4656d8f", - "sha256:1c88dfb9e0c77612febebb6ac69d44a8d81e3dc60f993215425b62c1161353f4", - "sha256:1e9dc350fb4c58adc64df3e0703ab076f60aac06e67d48b3848c23647ae4310e", - "sha256:247b9d16535acaa766d03037d8e8fb20866d054d3c7fbf6fd1f993f11fc60ca0", - "sha256:266270c6f6608340f6c9836a0fb9b367be61dde0c9a9a18d5ece97774105ff3e", - "sha256:34b9071c040d6fe45d9826cbbe3727d20d83f1b6110d219b83eb0e2a01d79488", - "sha256:3d7d1f8871998cdff5d2ff6a087e5e1780139abe2838e85b0b46b7ae6cc25399", - "sha256:461e3ae84cd90b30f8d533f07d87c00379644205b1d33a5ea03381edc4b69431", - "sha256:464b423bc2a009088f19bdf1f232299e8b6917963e2b7e1d277da5041f33a779", - "sha256:491b1917afdd8638a05b611a56d46587d5a632cabead889a5440f7c638bc6ed9", - "sha256:4a1b1208102be6e851f20066bf0e7a96b7d48a07c9b0cfe6d0d4545c2f6cadab", - "sha256:575bcaf957a25d1194903a10bc9f316c136c19f24e0985a2b9b5608bdf5dbfe0", - "sha256:5c6b28956b7b232ae801406e529ad7b350d3f09a4fde958dfdf3c0520cdde0dd", - "sha256:5d16edfc3fc09968e09ddffada434b3bf989bf4911535e04eada58469873e28e", - "sha256:5fd1810973a375ca0e097dee059c407913ba35723b111df75671a1976efa04bc", - "sha256:67b7f17679fa62ec82b7e3e611c43a016b887bd64fb933b3ae8638583006c6d6", - "sha256:68ce4788b7d93e47f84edd3f1f95acdcd142ae60bc0e5493bfd120683d2d4316", - "sha256:6d3364b999c62f539cd403f8123ae426da946e142312a514162adb2addd8d808", - "sha256:6e739530c662a8d6d42c37c2ed52a6f0932c2d4a3e8c1f90692ad0ce1274abe0", - "sha256:6fdd887f17c2f4572ce548461e4f96396681212d858cae7bd52ba3310bc6f00f", - "sha256:78e6ad33cf2e2e80c5dfaaa0beec3d61face0fb650557100ee36db808bfa6843", - "sha256:884c3f9d42d7218304bc74a8a7693d172685c84bd7ab2bab1ee567b769696df9", - "sha256:8d8faf05be5ec8e02a4d86f616fc2a0322ff4a4ce26c0f09d9f7fb5330a35c95", - "sha256:999c71939aad2780f003979b25ac5b8f2df651dac7b38fb8ce6c46ba5abe6ae9", - "sha256:99ad97258985328b4f207a5e777c1b44a83bfe7cf1f87b99f9c11d4ee477c4de", - "sha256:9e6c93b5b2dbcedad20a2f18ec22cae47da0d705d454308063421a3b290d9ea4", - "sha256:ab459a1cbbf18e8698399c595a01f6dcc5c138220ca3ea9e7e6126232d102bb4", - "sha256:b69303ceb2e4d4f146bf82fda78891ef7bcd80c41bf16bfca3d0d7eb545448aa", - "sha256:b7caf9b241464c404613512d5594a6e2ff0cc9cb5615c9475cc1d9b514218ae8", - "sha256:b95a225d4948b26a28c08307a60ac00fb8671b14f2047fc5476613252a129776", - "sha256:bd2f1ae63998da104f16a8b788f685e55d65760cd1929518fd94cd682bf03e41", - "sha256:be16975d94c320432657ad2402f6760990cb640c161ae6da1363051805fa8108", - "sha256:ce96dd400486e80ac7d195b2d800b03e3e6a787e2a522bfb83755938465a819e", - "sha256:dbd50d0a0539ae2e96e537553aff6d02c10ed165ef40c65b0e27e744a0f10af8", - "sha256:dd10c26b4eadae44783c45ad6655220426f971c61d9b239e6f7b16d5cdaaa727", - "sha256:ebeac59e9e1eb4b84940d076d9f9a6cec0064e241818bcb6e32124cc5c3e377a" + "sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2", + "sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9", + "sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9", + "sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4", + "sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce", + "sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7", + "sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f", + "sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922", + "sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4", + "sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e", + "sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b", + "sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619", + "sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205", + "sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480", + "sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965", + "sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c", + "sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd", + "sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5", + "sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f", + "sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc", + "sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec", + "sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd", + "sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b", + "sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9", + "sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe", + "sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce", + "sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609", + "sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8", + "sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0", + "sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f", + "sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8", + "sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b", + "sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364", + "sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040", + "sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f", + "sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083", + "sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df", + "sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba", + "sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445", + "sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da", + "sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3", + "sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72", + "sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02", + "sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985" ], "markers": "python_version >= '3.9'", - "version": "==1.2.0" + "version": "==1.2.1" }, "cryptography": { "hashes": [ - "sha256:0c327cac00f082013c7c9fb6c46b7cc9fa3c288ca702c74773968173bda421bf", - "sha256:0d2a6a598847c46e3e321a7aef8af1436f11c27f1254933746304ff014664d84", - "sha256:227ec057cd32a41c6651701abc0328135e472ed450f47c2766f23267b792a88e", - "sha256:22892cc830d8b2c89ea60148227631bb96a7da0c1b722f2aac8824b1b7c0b6b8", - "sha256:392cb88b597247177172e02da6b7a63deeff1937fa6fec3bbf902ebd75d97ec7", - "sha256:3be3ca726e1572517d2bef99a818378bbcf7d7799d5372a46c79c29eb8d166c1", - "sha256:573eb7128cbca75f9157dcde974781209463ce56b5804983e11a1c462f0f4e88", - "sha256:580afc7b7216deeb87a098ef0674d6ee34ab55993140838b14c9b83312b37b86", - "sha256:5a70187954ba7292c7876734183e810b728b4f3965fbe571421cb2434d279179", - "sha256:73801ac9736741f220e20435f84ecec75ed70eda90f781a148f1bad546963d81", - "sha256:7d208c21e47940369accfc9e85f0de7693d9a5d843c2509b3846b2db170dfd20", - "sha256:8254962e6ba1f4d2090c44daf50a547cd5f0bf446dc658a8e5f8156cae0d8548", - "sha256:88417bff20162f635f24f849ab182b092697922088b477a7abd6664ddd82291d", - "sha256:a48e74dad1fb349f3dc1d449ed88e0017d792997a7ad2ec9587ed17405667e6d", - "sha256:b948e09fe5fb18517d99994184854ebd50b57248736fd4c720ad540560174ec5", - "sha256:c707f7afd813478e2019ae32a7c49cd932dd60ab2d2a93e796f68236b7e1fbf1", - "sha256:d38e6031e113b7421db1de0c1b1f7739564a88f1684c6b89234fbf6c11b75147", - "sha256:d3977f0e276f6f5bf245c403156673db103283266601405376f075c849a0b936", - "sha256:da6a0ff8f1016ccc7477e6339e1d50ce5f59b88905585f77193ebd5068f1e797", - "sha256:e270c04f4d9b5671ebcc792b3ba5d4488bf7c42c3c241a3748e2599776f29696", - "sha256:e886098619d3815e0ad5790c973afeee2c0e6e04b4da90b88e6bd06e2a0b1b72", - "sha256:ec3b055ff8f1dce8e6ef28f626e0972981475173d7973d63f271b29c8a2897da", - "sha256:fba1e91467c65fe64a82c689dc6cf58151158993b13eb7a7f3f4b7f395636723" + "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709", + "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069", + "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2", + "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b", + "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e", + "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70", + "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778", + "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22", + "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895", + "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf", + "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431", + "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f", + "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947", + "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74", + "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc", + "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66", + "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66", + "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf", + "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f", + "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5", + "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e", + "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f", + "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55", + "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1", + "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47", + "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5", + "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0" ], "markers": "python_version >= '3.7'", - "version": "==41.0.5" + "version": "==43.0.0" }, "debugpy": { "hashes": [ - "sha256:125b9a637e013f9faac0a3d6a82bd17c8b5d2c875fb6b7e2772c5aba6d082332", - "sha256:12af2c55b419521e33d5fb21bd022df0b5eb267c3e178f1d374a63a2a6bdccd0", - "sha256:3c6fb41c98ec51dd010d7ed650accfd07a87fe5e93eca9d5f584d0578f28f35f", - "sha256:46ab6780159eeabb43c1495d9c84cf85d62975e48b6ec21ee10c95767c0590aa", - "sha256:57161629133113c97b387382045649a2b985a348f0c9366e22217c87b68b73c6", - "sha256:5d9de202f5d42e62f932507ee8b21e30d49aae7e46d5b1dd5c908db1d7068637", - "sha256:60009b132c91951354f54363f8ebdf7457aeb150e84abba5ae251b8e9f29a8a6", - "sha256:61eab4a4c8b6125d41a34bad4e5fe3d2cc145caecd63c3fe953be4cc53e65bf8", - "sha256:7fb95ca78f7ac43393cd0e0f2b6deda438ec7c5e47fa5d38553340897d2fbdfb", - "sha256:8cd0197141eb9e8a4566794550cfdcdb8b3db0818bdf8c49a8e8f8053e56e38b", - "sha256:9c9b0ac1ce2a42888199df1a1906e45e6f3c9555497643a85e0bf2406e3ffbc4", - "sha256:a64093656c4c64dc6a438e11d59369875d200bd5abb8f9b26c1f5f723622e153", - "sha256:a8b7a2fd27cd9f3553ac112f356ad4ca93338feadd8910277aff71ab24d8775f", - "sha256:b05a6b503ed520ad58c8dc682749113d2fd9f41ffd45daec16e558ca884008cd", - "sha256:bdc5ef99d14b9c0fcb35351b4fbfc06ac0ee576aeab6b2511702e5a648a2e595", - "sha256:e3412f9faa9ade82aa64a50b602544efcba848c91384e9f93497a458767e6926", - "sha256:ef54404365fae8d45cf450d0544ee40cefbcb9cb85ea7afe89a963c27028261e", - "sha256:ef9ab7df0b9a42ed9c878afd3eaaff471fce3fa73df96022e1f5c9f8f8c87ada" + "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c", + "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226", + "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c", + "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3", + "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a", + "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a", + "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408", + "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44", + "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156", + "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a", + "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c", + "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7", + "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a", + "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf", + "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34", + "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0", + "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e", + "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb", + "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7", + "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b", + "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed", + "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406" ], "markers": "python_version >= '3.8'", - "version": "==1.8.0" + "version": "==1.8.5" }, "decorator": { "hashes": [ @@ -586,11 +613,11 @@ }, "exceptiongroup": { "hashes": [ - "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9", - "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3" + "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", + "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" ], "markers": "python_version < '3.11'", - "version": "==1.1.3" + "version": "==1.2.2" }, "executing": { "hashes": [ @@ -602,10 +629,10 @@ }, "fastjsonschema": { "hashes": [ - "sha256:b9fd1a2dd6971dbc7fee280a95bd199ae0dd9ce22beb91cc75e9c1c528a5170e", - "sha256:e25df6647e1bc4a26070b700897b07b542ec898dd4f1f6ea013e7f6a88417225" + "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23", + "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a" ], - "version": "==2.19.0" + "version": "==2.20.0" }, "fqdn": { "hashes": [ @@ -614,38 +641,69 @@ ], "version": "==1.5.1" }, + "h11": { + "hashes": [ + "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", + "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761" + ], + "markers": "python_version >= '3.7'", + "version": "==0.14.0" + }, + "httpcore": { + "hashes": [ + "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61", + "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.5" + }, + "httpx": { + "hashes": [ + "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", + "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5" + ], + "markers": "python_version >= '3.8'", + "version": "==0.27.0" + }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], "markers": "python_version >= '3.5'", - "version": "==3.4" + "version": "==3.7" + }, + "importlib-metadata": { + "hashes": [ + "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369", + "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d" + ], + "markers": "python_version < '3.10'", + "version": "==8.2.0" }, "ipykernel": { "hashes": [ - "sha256:3ba3dc97424b87b31bb46586b5167b3161b32d7820b9201a9e698c71e271602c", - "sha256:553856658eb8430bbe9653ea041a41bff63e9606fc4628873fc92a6cf3abd404" + "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", + "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==6.26.0" + "version": "==6.29.5" }, "ipython": { "hashes": [ - "sha256:126bb57e1895594bb0d91ea3090bbd39384f6fe87c3d57fd558d0670f50339bb", - "sha256:1e4d1d666a023e3c93585ba0d8e962867f7a111af322efff6b9c58062b3e5444" + "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", + "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397" ], "markers": "python_version >= '3.9'", - "version": "==8.17.2" + "version": "==8.18.1" }, "ipywidgets": { "hashes": [ - "sha256:2b88d728656aea3bbfd05d32c747cfd0078f9d7e159cf982433b58ad717eed7f", - "sha256:40211efb556adec6fa450ccc2a77d59ca44a060f4f9f136833df59c9f538e6e8" + "sha256:efafd18f7a142248f7cb0ba890a68b96abd4d6e88ddbda483c9130d12667eaf2", + "sha256:f5f9eeaae082b1823ce9eac2575272952f40d748893972956dc09700a6392d9c" ], "markers": "python_version >= '3.7'", - "version": "==8.1.1" + "version": "==8.1.3" }, "isoduration": { "hashes": [ @@ -664,41 +722,42 @@ }, "jinja2": { "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" ], "markers": "python_version >= '3.7'", - "version": "==3.1.2" + "version": "==3.1.4" }, "json5": { "hashes": [ - "sha256:740c7f1b9e584a468dbb2939d8d458db3427f2c93ae2139d05f47e453eae964f", - "sha256:9ed66c3a6ca3510a976a9ef9b8c0787de24802724ab1860bc0153c7fdd589b02" + "sha256:34ed7d834b1341a86987ed52f3f76cd8ee184394906b6e22a1e0deb9ab294e8f", + "sha256:548e41b9be043f9426776f05df8635a00fe06104ea51ed24b67f908856e151ae" ], - "version": "==0.9.14" + "markers": "python_version >= '3.8'", + "version": "==0.9.25" }, "jsonpointer": { "hashes": [ - "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a", - "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88" + "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", + "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef" ], - "version": "==2.4" + "version": "==3.0.0" }, "jsonschema": { "hashes": [ - "sha256:c9ff4d7447eed9592c23a12ccee508baf0dd0d59650615e847feb6cdca74f392", - "sha256:eee9e502c788e89cb166d4d37f43084e3b64ab405c795c03d343a4dbc2c810fc" + "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", + "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566" ], "markers": "python_version >= '3.8'", - "version": "==4.19.2" + "version": "==4.23.0" }, "jsonschema-specifications": { "hashes": [ - "sha256:c9b234904ffe02f079bf91b14d79987faa685fd4b39c377a0996954c0090b9ca", - "sha256:f596778ab612b3fd29f72ea0d990393d0540a5aab18bf0407a46632eab540779" + "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", + "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c" ], "markers": "python_version >= '3.8'", - "version": "==2023.11.1" + "version": "==2023.12.1" }, "jupyter": { "hashes": [ @@ -711,20 +770,19 @@ }, "jupyter-bokeh": { "hashes": [ - "sha256:2da8c3ddc734d15737bf06126d9e31e84d30f18ac3da3a3f95be40a95a054c87", - "sha256:676d74bd8b95c7467d5e7ea1c954b306c7768b7bfa2bb3dd32e64efdf7dc09ee" + "sha256:1110076c14c779071cf492646a1a871aefa8a477261e4721327a666e65df1a2c", + "sha256:a33d6ab85588f13640b30765fa15d1111b055cbe44f67a65ca57d3593af8245d" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==3.0.7" + "version": "==4.0.5" }, "jupyter-client": { "hashes": [ - "sha256:0642244bb83b4764ae60d07e010e15f0e2d275ec4e918a8f7b80fbbef3ca60c7", - "sha256:909c474dbe62582ae62b758bca86d6518c85234bdee2d908c778db6d72f39d99" + "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df", + "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f" ], "markers": "python_version >= '3.8'", - "version": "==8.6.0" + "version": "==8.6.2" }, "jupyter-console": { "hashes": [ @@ -736,91 +794,91 @@ }, "jupyter-core": { "hashes": [ - "sha256:880b86053bf298a8724994f95e99b99130659022a4f7f45f563084b6223861d3", - "sha256:e11e02cd8ae0a9de5c6c44abf5727df9f2581055afe00b22183f621ba3585805" + "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", + "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9" ], "markers": "python_version >= '3.8'", - "version": "==5.5.0" + "version": "==5.7.2" }, "jupyter-events": { "hashes": [ - "sha256:81ad2e4bc710881ec274d31c6c50669d71bbaa5dd9d01e600b56faa85700d399", - "sha256:d853b3c10273ff9bc8bb8b30076d65e2c9685579db736873de6c2232dde148bf" + "sha256:4b72130875e59d57716d327ea70d3ebc3af1944d3717e5a498b8a06c6c159960", + "sha256:670b8229d3cc882ec782144ed22e0d29e1c2d639263f92ca8383e66682845e22" ], "markers": "python_version >= '3.8'", - "version": "==0.9.0" + "version": "==0.10.0" }, "jupyter-lsp": { "hashes": [ - "sha256:8ebbcb533adb41e5d635eb8fe82956b0aafbf0fd443b6c4bfa906edeeb8635a1", - "sha256:9e06b8b4f7dd50300b70dd1a78c0c3b0c3d8fa68e0f2d8a5d1fbab62072aca3f" + "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da", + "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001" ], "markers": "python_version >= '3.8'", - "version": "==2.2.0" + "version": "==2.2.5" }, "jupyter-server": { "hashes": [ - "sha256:47b8f5e63440125cb1bb8957bf12b18453ee5ed9efe42d2f7b2ca66a7019a278", - "sha256:dde56c9bc3cb52d7b72cc0f696d15d7163603526f1a758eb4a27405b73eab2a5" + "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd", + "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b" ], "markers": "python_version >= '3.8'", - "version": "==2.10.0" + "version": "==2.14.2" }, "jupyter-server-terminals": { "hashes": [ - "sha256:57ab779797c25a7ba68e97bcfb5d7740f2b5e8a83b5e8102b10438041a7eac5d", - "sha256:75779164661cec02a8758a5311e18bb8eb70c4e86c6b699403100f1585a12a36" + "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", + "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269" ], "markers": "python_version >= '3.8'", - "version": "==0.4.4" + "version": "==0.5.3" }, "jupyterlab": { "hashes": [ - "sha256:2ff5aa2a51eb21df241d6011c236e88bd1ff9a5dbb75bebc54472f9c18bfffa4", - "sha256:c4fe93f977bcc987bd395d7fae5ab02e0c042bf4e0f7c95196f3e2e578c2fb3a" + "sha256:343a979fb9582fd08c8511823e320703281cd072a0049bcdafdc7afeda7f2537", + "sha256:807a7ec73637744f879e112060d4b9d9ebe028033b7a429b2d1f4fc523d00245" ], "markers": "python_version >= '3.8'", - "version": "==4.0.8" + "version": "==4.2.4" }, "jupyterlab-pygments": { "hashes": [ - "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f", - "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d" + "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", + "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780" ], - "markers": "python_version >= '3.7'", - "version": "==0.2.2" + "markers": "python_version >= '3.8'", + "version": "==0.3.0" }, "jupyterlab-server": { "hashes": [ - "sha256:6491283b0000698eae1a38c48507930560dfcf7461aea0015368698aab34dd9c", - "sha256:dce9714d91fb3e53d2b37d0e0619fa26ed223c8e7b8c81cca112926de19b53a4" + "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", + "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4" ], "markers": "python_version >= '3.8'", - "version": "==2.25.1" + "version": "==2.27.3" }, "jupyterlab-widgets": { "hashes": [ - "sha256:3cf5bdf5b897bf3bccf1c11873aa4afd776d7430200f765e0686bd352487b58d", - "sha256:6005a4e974c7beee84060fdfba341a3218495046de8ae3ec64888e5fe19fdb4c" + "sha256:78287fd86d20744ace330a61625024cf5521e1c012a352ddc0a3cdc2348becd0", + "sha256:dd5ac679593c969af29c9bed054c24f26842baa51352114736756bc035deee27" ], "markers": "python_version >= '3.7'", - "version": "==3.0.9" + "version": "==3.0.11" }, "linkify-it-py": { "hashes": [ - "sha256:19f3060727842c254c808e99d465c80c49d2c7306788140987a1a7a29b0d6ad2", - "sha256:a3a24428f6c96f27370d7fe61d2ac0be09017be5190d68d8658233171f1b6541" + "sha256:68cda27e162e9215c17d786649d1da0021a451bdc436ef9e0fa0ba5234b9b048", + "sha256:6bcbc417b0ac14323382aef5c5192c0075bf8a9d6b41820a2b66371eac6b6d79" ], "markers": "python_version >= '3.7'", - "version": "==2.0.2" + "version": "==2.0.3" }, "markdown": { "hashes": [ - "sha256:5874b47d4ee3f0b14d764324d2c94c03ea66bee56f2d929da9f2508d65e722dc", - "sha256:b65d7beb248dc22f2e8a31fb706d93798093c308dc1aba295aedeb9d41a813bd" + "sha256:48f276f4d8cfb8ce6527c8f79e2ee29708508bf4d40aa410fbc3b4ee832c850f", + "sha256:ed4f41f6daecbeeb96e576ce414c41d2d876daa9a16cb35fa8ed8c2ddfad0224" ], "markers": "python_version >= '3.8'", - "version": "==3.5.1" + "version": "==3.6" }, "markdown-it-py": { "hashes": [ @@ -832,85 +890,85 @@ }, "markupsafe": { "hashes": [ - "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e", - "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e", - "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431", - "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686", - "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c", - "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559", - "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc", - "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb", - "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939", - "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c", - "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0", - "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4", - "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9", - "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575", - "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba", - "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d", - "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd", - "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3", - "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00", - "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155", - "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac", - "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52", - "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f", - "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8", - "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b", - "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007", - "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24", - "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea", - "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198", - "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0", - "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee", - "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be", - "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2", - "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1", - "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707", - "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6", - "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c", - "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58", - "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823", - "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779", - "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636", - "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c", - "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad", - "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee", - "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc", - "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2", - "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48", - "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7", - "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e", - "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b", - "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa", - "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5", - "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e", - "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb", - "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9", - "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57", - "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc", - "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc", - "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2", - "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11" + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" ], "markers": "python_version >= '3.7'", - "version": "==2.1.3" + "version": "==2.1.5" }, "matplotlib-inline": { "hashes": [ - "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", - "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304" + "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", + "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca" ], - "markers": "python_version >= '3.5'", - "version": "==0.1.6" + "markers": "python_version >= '3.8'", + "version": "==0.1.7" }, "mdit-py-plugins": { "hashes": [ - "sha256:b51b3bb70691f57f974e257e367107857a93b36f322a9e6d44ca5bf28ec2def9", - "sha256:d8ab27e9aed6c38aa716819fedfde15ca275715955f8a185a8e1cf90fb1d2c1b" + "sha256:1020dfe4e6bfc2c79fb49ae4e3f5b297f5ccd20f010187acc52af2921e27dc6a", + "sha256:834b8ac23d1cd60cec703646ffd22ae97b7955a6d596eb1d304be1e251ae499c" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==0.4.1" }, "mdurl": { "hashes": [ @@ -930,298 +988,321 @@ }, "nbclient": { "hashes": [ - "sha256:4b28c207877cf33ef3a9838cdc7a54c5ceff981194a82eac59d558f05487295e", - "sha256:a3a1ddfb34d4a9d17fc744d655962714a866639acd30130e9be84191cd97cd15" + "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09", + "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f" ], "markers": "python_full_version >= '3.8.0'", - "version": "==0.9.0" + "version": "==0.10.0" }, "nbconvert": { "hashes": [ - "sha256:abedc01cf543177ffde0bfc2a69726d5a478f6af10a332fc1bf29fcb4f0cf000", - "sha256:d1d417b7f34a4e38887f8da5bdfd12372adf3b80f995d57556cb0972c68909fe" + "sha256:05873c620fe520b6322bf8a5ad562692343fe3452abda5765c7a34b7d1aa3eb3", + "sha256:86ca91ba266b0a448dc96fa6c5b9d98affabde2867b363258703536807f9f7f4" ], "markers": "python_version >= '3.8'", - "version": "==7.11.0" + "version": "==7.16.4" }, "nbformat": { "hashes": [ - "sha256:1c5172d786a41b82bcfd0c23f9e6b6f072e8fb49c39250219e4acfff1efe89e9", - "sha256:5f98b5ba1997dff175e77e0c17d5c10a96eaed2cbd1de3533d1fc35d5e111192" + "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", + "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b" ], "markers": "python_version >= '3.8'", - "version": "==5.9.2" + "version": "==5.10.4" }, "nest-asyncio": { "hashes": [ - "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb", - "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d" + "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", + "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" ], "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==1.5.8" + "version": "==1.6.0" }, "notebook": { "hashes": [ - "sha256:0fe8f67102fea3744fedf652e4c15339390902ca70c5a31c4f547fa23da697cc", - "sha256:ec6113b06529019f7f287819af06c97a2baf7a95ac21a8f6e32192898e9f9a58" + "sha256:4287b6da59740b32173d01d641f763d292f49c30e7a51b89c46ba8473126341e", + "sha256:f45489a3995746f2195a137e0773e2130960b51c9ac3ce257dbc2705aab3a6ca" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==7.0.6" + "version": "==7.2.1" }, "notebook-shim": { "hashes": [ - "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7", - "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9" + "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", + "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb" ], "markers": "python_version >= '3.7'", - "version": "==0.2.3" + "version": "==0.2.4" }, "numpy": { "hashes": [ - "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a", - "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6", - "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2", - "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79", - "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9", - "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919", - "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d", - "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060", - "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75", - "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f", - "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe", - "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167", - "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef", - "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75", - "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3", - "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7", - "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7", - "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d", - "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b", - "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186", - "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0", - "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1", - "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6", - "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e", - "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523", - "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36", - "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841", - "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818", - "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00", - "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80", - "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440", - "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210", - "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8", - "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea", - "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec", - "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841" + "sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2", + "sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292", + "sha256:0792824ce2f7ea0c82ed2e4fecc29bb86bee0567a080dacaf2e0a01fe7654369", + "sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91", + "sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388", + "sha256:186ba67fad3c60dbe8a3abff3b67a91351100f2661c8e2a80364ae6279720299", + "sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd", + "sha256:436c8e9a4bdeeee84e3e59614d38c3dbd3235838a877af8c211cfcac8a80b8d3", + "sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2", + "sha256:4acc65dd65da28060e206c8f27a573455ed724e6179941edb19f97e58161bb69", + "sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68", + "sha256:546b7dd7e22f3c6861463bebb000646fa730e55df5ee4a0224408b5694cc6148", + "sha256:5671338034b820c8d58c81ad1dafc0ed5a00771a82fccc71d6438df00302094b", + "sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a", + "sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be", + "sha256:7d484292eaeb3e84a51432a94f53578689ffdea3f90e10c8b203a99be5af57d8", + "sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505", + "sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c", + "sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208", + "sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8", + "sha256:b44e6a09afc12952a7d2a58ca0a2429ee0d49a4f89d83a0a11052da696440e49", + "sha256:bb0d9a1aaf5f1cb7967320e80690a1d7ff69f1d47ebc5a9bea013e3a21faec95", + "sha256:c0b45c8b65b79337dee5134d038346d30e109e9e2e9d43464a2970e5c0e93229", + "sha256:c2e698cb0c6dda9372ea98a0344245ee65bdc1c9dd939cceed6bb91256837896", + "sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f", + "sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c", + "sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb", + "sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99", + "sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112", + "sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581", + "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd", + "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf" ], - "markers": "python_version >= '3.9'", - "version": "==1.26.2" + "index": "pypi", + "version": "==1.26" }, "overrides": { "hashes": [ - "sha256:3ad24583f86d6d7a49049695efe9933e67ba62f0c7625d53c59fa832ce4b8b7d", - "sha256:9502a3cca51f4fac40b5feca985b6703a5c1f6ad815588a7ca9e285b9dca6757" + "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", + "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49" ], "markers": "python_version >= '3.6'", - "version": "==7.4.0" + "version": "==7.7.0" }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "markers": "python_version >= '3.7'", - "version": "==23.2" + "markers": "python_version >= '3.8'", + "version": "==24.1" }, "pandas": { "hashes": [ - "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03", - "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82", - "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549", - "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71", - "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2", - "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683", - "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f", - "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a", - "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb", - "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb", - "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef", - "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e", - "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4", - "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e", - "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d", - "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042", - "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140", - "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f", - "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8", - "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58", - "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f", - "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d", - "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00", - "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2", - "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a" + "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863", + "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2", + "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1", + "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad", + "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db", + "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76", + "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51", + "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32", + "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08", + "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b", + "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4", + "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921", + "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288", + "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee", + "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0", + "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24", + "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99", + "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151", + "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd", + "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce", + "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57", + "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef", + "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54", + "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a", + "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238", + "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23", + "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772", + "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce", + "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad" ], "markers": "python_version >= '3.9'", - "version": "==2.1.3" + "version": "==2.2.2" }, "pandocfilters": { "hashes": [ - "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38", - "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f" + "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", + "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.5.0" + "version": "==1.5.1" }, "panel": { "hashes": [ - "sha256:9192a798b77b998c294dbfc342f89b2f7d6171aae4995468a88a6f23c8d06a53", - "sha256:d50abe3361239516b265444fef3a3c9f2faa0b0cfb671849c767829b857c7a5f" + "sha256:a6dbddd65e9e68c54a9b683f103b79b48fcea5dd9f463b81a783ea11520fe9cb", + "sha256:a7c9be109b57bdea16a143ce6a897500e1172a28b8a7c0dcfd5b7f61c616ea42" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.3.1" + "version": "==1.4.5" }, "param": { "hashes": [ - "sha256:4bfc94c0e4127626fa833e30c71c91ea73e7675b80c26dbdd4a6e5a8f6dc46db", - "sha256:7943a04607822efd46e96e1827dc5fa929a2fc3b1fe9fc7b7dca7d17a8031a5b" + "sha256:3b1da14abafa75bfd908572378a58696826b3719a723bc31b40ffff2e9a5c852", + "sha256:81066d040526fbaa44b6419f3e92348fa8856ea44c8d3915e9245937ddabe2d6" ], "markers": "python_version >= '3.8'", - "version": "==2.0.1" + "version": "==2.1.1" }, "paramiko": { "hashes": [ - "sha256:6a3777a961ac86dbef375c5f5b8d50014a1a96d0fd7f054a43bc880134b0ff77", - "sha256:b7bc5340a43de4287bbe22fe6de728aa2c22468b2a849615498dd944c2f275eb" + "sha256:43f0b51115a896f9c00f59618023484cb3a14b98bbceab43394a39c6739b7ee7", + "sha256:aac08f26a31dc4dffd92821527d1682d99d52f9ef6851968114a8728f3c274d3" ], "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==3.3.1" + "version": "==3.4.0" }, "parso": { "hashes": [ - "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", - "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", + "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" ], "markers": "python_version >= '3.6'", - "version": "==0.8.3" + "version": "==0.8.4" }, "pexpect": { "hashes": [ - "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937", - "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c" + "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", + "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" ], "markers": "sys_platform != 'win32'", - "version": "==4.8.0" + "version": "==4.9.0" }, "pillow": { "hashes": [ - "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d", - "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de", - "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616", - "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839", - "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099", - "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a", - "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219", - "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106", - "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b", - "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412", - "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b", - "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7", - "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2", - "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7", - "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14", - "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f", - "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27", - "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57", - "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262", - "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28", - "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610", - "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172", - "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273", - "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e", - "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d", - "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818", - "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f", - "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9", - "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01", - "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7", - "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651", - "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312", - "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80", - "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666", - "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061", - "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b", - "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992", - "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593", - "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4", - "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db", - "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba", - "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd", - "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e", - "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212", - "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb", - "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2", - "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34", - "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256", - "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f", - "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2", - "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38", - "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996", - "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a", - "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793" - ], - "markers": "python_version >= '3.8'", - "version": "==10.1.0" + "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", + "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", + "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", + "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", + "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", + "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", + "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", + "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", + "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", + "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", + "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", + "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", + "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", + "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", + "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", + "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", + "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", + "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", + "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", + "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", + "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", + "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", + "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", + "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", + "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", + "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", + "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", + "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", + "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", + "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", + "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", + "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", + "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", + "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", + "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", + "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", + "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", + "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", + "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", + "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", + "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", + "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", + "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", + "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", + "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", + "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", + "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", + "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", + "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", + "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", + "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", + "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", + "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", + "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", + "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", + "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", + "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", + "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", + "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", + "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", + "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", + "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", + "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", + "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", + "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", + "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", + "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", + "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", + "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", + "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", + "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", + "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", + "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", + "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", + "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", + "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", + "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", + "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", + "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", + "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" + ], + "markers": "python_version >= '3.8'", + "version": "==10.4.0" }, "platformdirs": { "hashes": [ - "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b", - "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731" + "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", + "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" ], - "markers": "python_version >= '3.7'", - "version": "==4.0.0" + "markers": "python_version >= '3.8'", + "version": "==4.2.2" }, "prometheus-client": { "hashes": [ - "sha256:35f7a8c22139e2bb7ca5a698e92d38145bc8dc74c1c0bf56f25cca886a764e17", - "sha256:8de3ae2755f890826f4b6479e5571d4f74ac17a81345fe69a6778fdb92579184" + "sha256:287629d00b147a32dcb2be0b9df905da599b2d82f80377083ec8463309a4bb89", + "sha256:cde524a85bce83ca359cc837f28b8c0db5cac7aa653a588fd7e84ba061c329e7" ], "markers": "python_version >= '3.8'", - "version": "==0.18.0" + "version": "==0.20.0" }, "prompt-toolkit": { "hashes": [ - "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0", - "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2" + "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10", + "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.0.41" + "version": "==3.0.47" }, "psutil": { "hashes": [ - "sha256:10e8c17b4f898d64b121149afb136c53ea8b68c7531155147867b7b1ac9e7e28", - "sha256:18cd22c5db486f33998f37e2bb054cc62fd06646995285e02a51b1e08da97017", - "sha256:3ebf2158c16cc69db777e3c7decb3c0f43a7af94a60d72e87b2823aebac3d602", - "sha256:51dc3d54607c73148f63732c727856f5febec1c7c336f8f41fcbd6315cce76ac", - "sha256:6e5fb8dc711a514da83098bc5234264e551ad980cec5f85dabf4d38ed6f15e9a", - "sha256:70cb3beb98bc3fd5ac9ac617a327af7e7f826373ee64c80efd4eb2856e5051e9", - "sha256:748c9dd2583ed86347ed65d0035f45fa8c851e8d90354c122ab72319b5f366f4", - "sha256:91ecd2d9c00db9817a4b4192107cf6954addb5d9d67a969a4f436dbc9200f88c", - "sha256:92e0cc43c524834af53e9d3369245e6cc3b130e78e26100d1f63cdb0abeb3d3c", - "sha256:a6f01f03bf1843280f4ad16f4bde26b817847b4c1a0db59bf6419807bc5ce05c", - "sha256:c69596f9fc2f8acd574a12d5f8b7b1ba3765a641ea5d60fb4736bf3c08a8214a", - "sha256:ca2780f5e038379e520281e4c032dddd086906ddff9ef0d1b9dcf00710e5071c", - "sha256:daecbcbd29b289aac14ece28eca6a3e60aa361754cf6da3dfb20d4d32b6c7f57", - "sha256:e4b92ddcd7dd4cdd3f900180ea1e104932c7bce234fb88976e2a3b296441225a", - "sha256:fb8a697f11b0f5994550555fcfe3e69799e5b060c8ecf9e2f75c69302cc35c0d", - "sha256:ff18b8d1a784b810df0b0fff3bcb50ab941c3b8e2c8de5726f9c71c601c611aa" + "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", + "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0", + "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c", + "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", + "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", + "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c", + "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", + "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3", + "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", + "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", + "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6", + "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", + "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c", + "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", + "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", + "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14", + "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==5.9.6" + "version": "==6.0.0" }, "ptyprocess": { "hashes": [ @@ -1233,10 +1314,10 @@ }, "pure-eval": { "hashes": [ - "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350", - "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3" + "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", + "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42" ], - "version": "==0.2.2" + "version": "==0.2.3" }, "pybela": { "editable": true, @@ -1244,18 +1325,19 @@ }, "pycparser": { "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], - "version": "==2.21" + "markers": "python_version >= '3.8'", + "version": "==2.22" }, "pygments": { "hashes": [ - "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", - "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" ], - "markers": "python_version >= '3.7'", - "version": "==2.16.1" + "markers": "python_version >= '3.8'", + "version": "==2.18.0" }, "pynacl": { "hashes": [ @@ -1275,11 +1357,11 @@ }, "python-dateutil": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" + "version": "==2.9.0.post0" }, "python-json-logger": { "hashes": [ @@ -1291,181 +1373,200 @@ }, "pytz": { "hashes": [ - "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b", - "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7" + "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", + "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" ], - "version": "==2023.3.post1" + "version": "==2024.1" }, "pyviz-comms": { "hashes": [ - "sha256:91c967151b1e4d436c458c147a31991a42fbe7567e49176e4eb5b8dc8e20f1ff", - "sha256:f4ca91e4157a64e3abed7cc249e60b9a8d2532f8832f1cb075914d19337d2ba6" + "sha256:fd26951eebc7950106d481655d91ba06296d4cf352dffb1d03f88f959832448e", + "sha256:fde4a017c2213ecee63a9a6741431c845e42a5c7b1588e4a7ba2e4370c583728" ], "markers": "python_version >= '3.8'", - "version": "==3.0.0" + "version": "==3.0.3" }, "pyyaml": { "hashes": [ - "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5", - "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc", - "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df", - "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741", - "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206", - "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27", - "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595", - "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62", - "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98", - "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696", - "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290", - "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9", - "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d", - "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6", - "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867", - "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47", - "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", - "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6", - "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3", - "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007", - "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938", - "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0", - "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c", - "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735", - "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d", - "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28", - "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", - "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", - "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", - "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", - "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", - "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", - "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0", - "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515", - "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c", - "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c", - "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924", - "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34", - "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43", - "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859", - "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673", - "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54", - "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a", - "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b", - "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab", - "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa", - "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c", - "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585", - "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d", - "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f" + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" ], - "markers": "python_version >= '3.6'", - "version": "==6.0.1" + "markers": "python_version >= '3.8'", + "version": "==6.0.2" }, "pyzmq": { "hashes": [ - "sha256:019e59ef5c5256a2c7378f2fb8560fc2a9ff1d315755204295b2eab96b254d0a", - "sha256:034239843541ef7a1aee0c7b2cb7f6aafffb005ede965ae9cbd49d5ff4ff73cf", - "sha256:03b3f49b57264909aacd0741892f2aecf2f51fb053e7d8ac6767f6c700832f45", - "sha256:047a640f5c9c6ade7b1cc6680a0e28c9dd5a0825135acbd3569cc96ea00b2505", - "sha256:04ccbed567171579ec2cebb9c8a3e30801723c575601f9a990ab25bcac6b51e2", - "sha256:057e824b2aae50accc0f9a0570998adc021b372478a921506fddd6c02e60308e", - "sha256:11baebdd5fc5b475d484195e49bae2dc64b94a5208f7c89954e9e354fc609d8f", - "sha256:11c1d2aed9079c6b0c9550a7257a836b4a637feb334904610f06d70eb44c56d2", - "sha256:11d58723d44d6ed4dd677c5615b2ffb19d5c426636345567d6af82be4dff8a55", - "sha256:12720a53e61c3b99d87262294e2b375c915fea93c31fc2336898c26d7aed34cd", - "sha256:17ef5f01d25b67ca8f98120d5fa1d21efe9611604e8eb03a5147360f517dd1e2", - "sha256:18d43df3f2302d836f2a56f17e5663e398416e9dd74b205b179065e61f1a6edf", - "sha256:1a5d26fe8f32f137e784f768143728438877d69a586ddeaad898558dc971a5ae", - "sha256:1af379b33ef33757224da93e9da62e6471cf4a66d10078cf32bae8127d3d0d4a", - "sha256:1ccf825981640b8c34ae54231b7ed00271822ea1c6d8ba1090ebd4943759abf5", - "sha256:21eb4e609a154a57c520e3d5bfa0d97e49b6872ea057b7c85257b11e78068222", - "sha256:2243700cc5548cff20963f0ca92d3e5e436394375ab8a354bbea2b12911b20b0", - "sha256:255ca2b219f9e5a3a9ef3081512e1358bd4760ce77828e1028b818ff5610b87b", - "sha256:259c22485b71abacdfa8bf79720cd7bcf4b9d128b30ea554f01ae71fdbfdaa23", - "sha256:25f0e6b78220aba09815cd1f3a32b9c7cb3e02cb846d1cfc526b6595f6046618", - "sha256:273bc3959bcbff3f48606b28229b4721716598d76b5aaea2b4a9d0ab454ec062", - "sha256:292fe3fc5ad4a75bc8df0dfaee7d0babe8b1f4ceb596437213821f761b4589f9", - "sha256:2ca57a5be0389f2a65e6d3bb2962a971688cbdd30b4c0bd188c99e39c234f414", - "sha256:2d163a18819277e49911f7461567bda923461c50b19d169a062536fffe7cd9d2", - "sha256:2d81f1ddae3858b8299d1da72dd7d19dd36aab654c19671aa8a7e7fb02f6638a", - "sha256:2f957ce63d13c28730f7fd6b72333814221c84ca2421298f66e5143f81c9f91f", - "sha256:330f9e188d0d89080cde66dc7470f57d1926ff2fb5576227f14d5be7ab30b9fa", - "sha256:34c850ce7976d19ebe7b9d4b9bb8c9dfc7aac336c0958e2651b88cbd46682123", - "sha256:35b5ab8c28978fbbb86ea54958cd89f5176ce747c1fb3d87356cf698048a7790", - "sha256:3669cf8ee3520c2f13b2e0351c41fea919852b220988d2049249db10046a7afb", - "sha256:381469297409c5adf9a0e884c5eb5186ed33137badcbbb0560b86e910a2f1e76", - "sha256:3d0a409d3b28607cc427aa5c30a6f1e4452cc44e311f843e05edb28ab5e36da0", - "sha256:44e58a0554b21fc662f2712814a746635ed668d0fbc98b7cb9d74cb798d202e6", - "sha256:458dea649f2f02a0b244ae6aef8dc29325a2810aa26b07af8374dc2a9faf57e3", - "sha256:48e466162a24daf86f6b5ca72444d2bf39a5e58da5f96370078be67c67adc978", - "sha256:49d238cf4b69652257db66d0c623cd3e09b5d2e9576b56bc067a396133a00d4a", - "sha256:4ca1ed0bb2d850aa8471387882247c68f1e62a4af0ce9c8a1dbe0d2bf69e41fb", - "sha256:52533489f28d62eb1258a965f2aba28a82aa747202c8fa5a1c7a43b5db0e85c1", - "sha256:548d6482dc8aadbe7e79d1b5806585c8120bafa1ef841167bc9090522b610fa6", - "sha256:5619f3f5a4db5dbb572b095ea3cb5cc035335159d9da950830c9c4db2fbb6995", - "sha256:57459b68e5cd85b0be8184382cefd91959cafe79ae019e6b1ae6e2ba8a12cda7", - "sha256:5a34d2395073ef862b4032343cf0c32a712f3ab49d7ec4f42c9661e0294d106f", - "sha256:61706a6b6c24bdece85ff177fec393545a3191eeda35b07aaa1458a027ad1304", - "sha256:724c292bb26365659fc434e9567b3f1adbdb5e8d640c936ed901f49e03e5d32e", - "sha256:73461eed88a88c866656e08f89299720a38cb4e9d34ae6bf5df6f71102570f2e", - "sha256:76705c9325d72a81155bb6ab48d4312e0032bf045fb0754889133200f7a0d849", - "sha256:76c1c8efb3ca3a1818b837aea423ff8a07bbf7aafe9f2f6582b61a0458b1a329", - "sha256:77a41c26205d2353a4c94d02be51d6cbdf63c06fbc1295ea57dad7e2d3381b71", - "sha256:79986f3b4af059777111409ee517da24a529bdbd46da578b33f25580adcff728", - "sha256:7cff25c5b315e63b07a36f0c2bab32c58eafbe57d0dce61b614ef4c76058c115", - "sha256:7f7e58effd14b641c5e4dec8c7dab02fb67a13df90329e61c869b9cc607ef752", - "sha256:820c4a08195a681252f46926de10e29b6bbf3e17b30037bd4250d72dd3ddaab8", - "sha256:87e34f31ca8f168c56d6fbf99692cc8d3b445abb5bfd08c229ae992d7547a92a", - "sha256:8f03d3f0d01cb5a018debeb412441996a517b11c5c17ab2001aa0597c6d6882c", - "sha256:90f26dc6d5f241ba358bef79be9ce06de58d477ca8485e3291675436d3827cf8", - "sha256:955215ed0604dac5b01907424dfa28b40f2b2292d6493445dd34d0dfa72586a8", - "sha256:985bbb1316192b98f32e25e7b9958088431d853ac63aca1d2c236f40afb17c83", - "sha256:a382372898a07479bd34bda781008e4a954ed8750f17891e794521c3e21c2e1c", - "sha256:a882ac0a351288dd18ecae3326b8a49d10c61a68b01419f3a0b9a306190baf69", - "sha256:aa8d6cdc8b8aa19ceb319aaa2b660cdaccc533ec477eeb1309e2a291eaacc43a", - "sha256:abc719161780932c4e11aaebb203be3d6acc6b38d2f26c0f523b5b59d2fc1996", - "sha256:abf34e43c531bbb510ae7e8f5b2b1f2a8ab93219510e2b287a944432fad135f3", - "sha256:ade6d25bb29c4555d718ac6d1443a7386595528c33d6b133b258f65f963bb0f6", - "sha256:afea96f64efa98df4da6958bae37f1cbea7932c35878b185e5982821bc883369", - "sha256:b1579413ae492b05de5a6174574f8c44c2b9b122a42015c5292afa4be2507f28", - "sha256:b3451108ab861040754fa5208bca4a5496c65875710f76789a9ad27c801a0075", - "sha256:b9af3757495c1ee3b5c4e945c1df7be95562277c6e5bccc20a39aec50f826cd0", - "sha256:bc16ac425cc927d0a57d242589f87ee093884ea4804c05a13834d07c20db203c", - "sha256:c2910967e6ab16bf6fbeb1f771c89a7050947221ae12a5b0b60f3bca2ee19bca", - "sha256:c2b92812bd214018e50b6380ea3ac0c8bb01ac07fcc14c5f86a5bb25e74026e9", - "sha256:c2f20ce161ebdb0091a10c9ca0372e023ce24980d0e1f810f519da6f79c60800", - "sha256:c56d748ea50215abef7030c72b60dd723ed5b5c7e65e7bc2504e77843631c1a6", - "sha256:c7c133e93b405eb0d36fa430c94185bdd13c36204a8635470cccc200723c13bb", - "sha256:c9c6c9b2c2f80747a98f34ef491c4d7b1a8d4853937bb1492774992a120f475d", - "sha256:cbc8df5c6a88ba5ae385d8930da02201165408dde8d8322072e3e5ddd4f68e22", - "sha256:cff084c6933680d1f8b2f3b4ff5bbb88538a4aac00d199ac13f49d0698727ecb", - "sha256:d2045d6d9439a0078f2a34b57c7b18c4a6aef0bee37f22e4ec9f32456c852c71", - "sha256:d20a0ddb3e989e8807d83225a27e5c2eb2260eaa851532086e9e0fa0d5287d83", - "sha256:d457aed310f2670f59cc5b57dcfced452aeeed77f9da2b9763616bd57e4dbaae", - "sha256:d89528b4943d27029a2818f847c10c2cecc79fa9590f3cb1860459a5be7933eb", - "sha256:db0b2af416ba735c6304c47f75d348f498b92952f5e3e8bff449336d2728795d", - "sha256:deee9ca4727f53464daf089536e68b13e6104e84a37820a88b0a057b97bba2d2", - "sha256:df27ffddff4190667d40de7beba4a950b5ce78fe28a7dcc41d6f8a700a80a3c0", - "sha256:e0c95ddd4f6e9fca4e9e3afaa4f9df8552f0ba5d1004e89ef0a68e1f1f9807c7", - "sha256:e1c1be77bc5fb77d923850f82e55a928f8638f64a61f00ff18a67c7404faf008", - "sha256:e1ffa1c924e8c72778b9ccd386a7067cddf626884fd8277f503c48bb5f51c762", - "sha256:e2400a94f7dd9cb20cd012951a0cbf8249e3d554c63a9c0cdfd5cbb6c01d2dec", - "sha256:e61f091c3ba0c3578411ef505992d356a812fb200643eab27f4f70eed34a29ef", - "sha256:e8a701123029cc240cea61dd2d16ad57cab4691804143ce80ecd9286b464d180", - "sha256:eadbefd5e92ef8a345f0525b5cfd01cf4e4cc651a2cffb8f23c0dd184975d787", - "sha256:f32260e556a983bc5c7ed588d04c942c9a8f9c2e99213fec11a031e316874c7e", - "sha256:f8115e303280ba09f3898194791a153862cbf9eef722ad8f7f741987ee2a97c7", - "sha256:fedbdc753827cf014c01dbbee9c3be17e5a208dcd1bf8641ce2cd29580d1f0d4" + "sha256:038ae4ffb63e3991f386e7fda85a9baab7d6617fe85b74a8f9cab190d73adb2b", + "sha256:05bacc4f94af468cc82808ae3293390278d5f3375bb20fef21e2034bb9a505b6", + "sha256:0614aed6f87d550b5cecb03d795f4ddbb1544b78d02a4bd5eecf644ec98a39f6", + "sha256:08f74904cb066e1178c1ec706dfdb5c6c680cd7a8ed9efebeac923d84c1f13b1", + "sha256:093a1a3cae2496233f14b57f4b485da01b4ff764582c854c0f42c6dd2be37f3d", + "sha256:0a1f6ea5b1d6cdbb8cfa0536f0d470f12b4b41ad83625012e575f0e3ecfe97f0", + "sha256:0e6cea102ffa16b737d11932c426f1dc14b5938cf7bc12e17269559c458ac334", + "sha256:263cf1e36862310bf5becfbc488e18d5d698941858860c5a8c079d1511b3b18e", + "sha256:28a8b2abb76042f5fd7bd720f7fea48c0fd3e82e9de0a1bf2c0de3812ce44a42", + "sha256:2ae7c57e22ad881af78075e0cea10a4c778e67234adc65c404391b417a4dda83", + "sha256:2cd0f4d314f4a2518e8970b6f299ae18cff7c44d4a1fc06fc713f791c3a9e3ea", + "sha256:2fa76ebcebe555cce90f16246edc3ad83ab65bb7b3d4ce408cf6bc67740c4f88", + "sha256:314d11564c00b77f6224d12eb3ddebe926c301e86b648a1835c5b28176c83eab", + "sha256:347e84fc88cc4cb646597f6d3a7ea0998f887ee8dc31c08587e9c3fd7b5ccef3", + "sha256:359c533bedc62c56415a1f5fcfd8279bc93453afdb0803307375ecf81c962402", + "sha256:393daac1bcf81b2a23e696b7b638eedc965e9e3d2112961a072b6cd8179ad2eb", + "sha256:3b3b8e36fd4c32c0825b4461372949ecd1585d326802b1321f8b6dc1d7e9318c", + "sha256:3c397b1b450f749a7e974d74c06d69bd22dd362142f370ef2bd32a684d6b480c", + "sha256:3d3146b1c3dcc8a1539e7cc094700b2be1e605a76f7c8f0979b6d3bde5ad4072", + "sha256:3ee647d84b83509b7271457bb428cc347037f437ead4b0b6e43b5eba35fec0aa", + "sha256:416ac51cabd54f587995c2b05421324700b22e98d3d0aa2cfaec985524d16f1d", + "sha256:451e16ae8bea3d95649317b463c9f95cd9022641ec884e3d63fc67841ae86dfe", + "sha256:45cb1a70eb00405ce3893041099655265fabcd9c4e1e50c330026e82257892c1", + "sha256:46d6800b45015f96b9d92ece229d92f2aef137d82906577d55fadeb9cf5fcb71", + "sha256:471312a7375571857a089342beccc1a63584315188560c7c0da7e0a23afd8a5c", + "sha256:471880c4c14e5a056a96cd224f5e71211997d40b4bf5e9fdded55dafab1f98f2", + "sha256:5384c527a9a004445c5074f1e20db83086c8ff1682a626676229aafd9cf9f7d1", + "sha256:57bb2acba798dc3740e913ffadd56b1fcef96f111e66f09e2a8db3050f1f12c8", + "sha256:58c33dc0e185dd97a9ac0288b3188d1be12b756eda67490e6ed6a75cf9491d79", + "sha256:59d0acd2976e1064f1b398a00e2c3e77ed0a157529779e23087d4c2fb8aaa416", + "sha256:5a6ed52f0b9bf8dcc64cc82cce0607a3dfed1dbb7e8c6f282adfccc7be9781de", + "sha256:5bc2431167adc50ba42ea3e5e5f5cd70d93e18ab7b2f95e724dd8e1bd2c38120", + "sha256:5cca7b4adb86d7470e0fc96037771981d740f0b4cb99776d5cb59cd0e6684a73", + "sha256:61dfa5ee9d7df297c859ac82b1226d8fefaf9c5113dc25c2c00ecad6feeeb04f", + "sha256:63c1d3a65acb2f9c92dce03c4e1758cc552f1ae5c78d79a44e3bb88d2fa71f3a", + "sha256:65c6e03cc0222eaf6aad57ff4ecc0a070451e23232bb48db4322cc45602cede0", + "sha256:67976d12ebfd61a3bc7d77b71a9589b4d61d0422282596cf58c62c3866916544", + "sha256:68a0a1d83d33d8367ddddb3e6bb4afbb0f92bd1dac2c72cd5e5ddc86bdafd3eb", + "sha256:6c5aeea71f018ebd3b9115c7cb13863dd850e98ca6b9258509de1246461a7e7f", + "sha256:754c99a9840839375ee251b38ac5964c0f369306eddb56804a073b6efdc0cd88", + "sha256:75a95c2358fcfdef3374cb8baf57f1064d73246d55e41683aaffb6cfe6862917", + "sha256:7688653574392d2eaeef75ddcd0b2de5b232d8730af29af56c5adf1df9ef8d6f", + "sha256:77ce6a332c7e362cb59b63f5edf730e83590d0ab4e59c2aa5bd79419a42e3449", + "sha256:7907419d150b19962138ecec81a17d4892ea440c184949dc29b358bc730caf69", + "sha256:79e45a4096ec8388cdeb04a9fa5e9371583bcb826964d55b8b66cbffe7b33c86", + "sha256:7bcbfbab4e1895d58ab7da1b5ce9a327764f0366911ba5b95406c9104bceacb0", + "sha256:80b0c9942430d731c786545da6be96d824a41a51742e3e374fedd9018ea43106", + "sha256:8b88641384e84a258b740801cd4dbc45c75f148ee674bec3149999adda4a8598", + "sha256:8d4dac7d97f15c653a5fedcafa82626bd6cee1450ccdaf84ffed7ea14f2b07a4", + "sha256:8d906d43e1592be4b25a587b7d96527cb67277542a5611e8ea9e996182fae410", + "sha256:8efb782f5a6c450589dbab4cb0f66f3a9026286333fe8f3a084399149af52f29", + "sha256:906e532c814e1d579138177a00ae835cd6becbf104d45ed9093a3aaf658f6a6a", + "sha256:90d4feb2e83dfe9ace6374a847e98ee9d1246ebadcc0cb765482e272c34e5820", + "sha256:911c43a4117915203c4cc8755e0f888e16c4676a82f61caee2f21b0c00e5b894", + "sha256:91d1a20bdaf3b25f3173ff44e54b1cfbc05f94c9e8133314eb2962a89e05d6e3", + "sha256:94c4262626424683feea0f3c34951d39d49d354722db2745c42aa6bb50ecd93b", + "sha256:96d7c1d35ee4a495df56c50c83df7af1c9688cce2e9e0edffdbf50889c167595", + "sha256:9869fa984c8670c8ab899a719eb7b516860a29bc26300a84d24d8c1b71eae3ec", + "sha256:98c03bd7f3339ff47de7ea9ac94a2b34580a8d4df69b50128bb6669e1191a895", + "sha256:995301f6740a421afc863a713fe62c0aaf564708d4aa057dfdf0f0f56525294b", + "sha256:998444debc8816b5d8d15f966e42751032d0f4c55300c48cc337f2b3e4f17d03", + "sha256:9a6847c92d9851b59b9f33f968c68e9e441f9a0f8fc972c5580c5cd7cbc6ee24", + "sha256:9bdfcb74b469b592972ed881bad57d22e2c0acc89f5e8c146782d0d90fb9f4bf", + "sha256:9f136a6e964830230912f75b5a116a21fe8e34128dcfd82285aa0ef07cb2c7bd", + "sha256:a0f0ab9df66eb34d58205913f4540e2ad17a175b05d81b0b7197bc57d000e829", + "sha256:a4b7a989c8f5a72ab1b2bbfa58105578753ae77b71ba33e7383a31ff75a504c4", + "sha256:a7b8aab50e5a288c9724d260feae25eda69582be84e97c012c80e1a5e7e03fb2", + "sha256:ad875277844cfaeca7fe299ddf8c8d8bfe271c3dc1caf14d454faa5cdbf2fa7a", + "sha256:add52c78a12196bc0fda2de087ba6c876ea677cbda2e3eba63546b26e8bf177b", + "sha256:b10163e586cc609f5f85c9b233195554d77b1e9a0801388907441aaeb22841c5", + "sha256:b24079a14c9596846bf7516fe75d1e2188d4a528364494859106a33d8b48be38", + "sha256:b281b5ff5fcc9dcbfe941ac5c7fcd4b6c065adad12d850f95c9d6f23c2652384", + "sha256:b3bb34bebaa1b78e562931a1687ff663d298013f78f972a534f36c523311a84d", + "sha256:b45e6445ac95ecb7d728604bae6538f40ccf4449b132b5428c09918523abc96d", + "sha256:ba0a31d00e8616149a5ab440d058ec2da621e05d744914774c4dde6837e1f545", + "sha256:baba2fd199b098c5544ef2536b2499d2e2155392973ad32687024bd8572a7d1c", + "sha256:bd13f0231f4788db619347b971ca5f319c5b7ebee151afc7c14632068c6261d3", + "sha256:bd3f6329340cef1c7ba9611bd038f2d523cea79f09f9c8f6b0553caba59ec562", + "sha256:bdeb2c61611293f64ac1073f4bf6723b67d291905308a7de9bb2ca87464e3273", + "sha256:bef24d3e4ae2c985034439f449e3f9e06bf579974ce0e53d8a507a1577d5b2ab", + "sha256:c0665d85535192098420428c779361b8823d3d7ec4848c6af3abb93bc5c915bf", + "sha256:c5668dac86a869349828db5fc928ee3f58d450dce2c85607067d581f745e4fb1", + "sha256:c9b9305004d7e4e6a824f4f19b6d8f32b3578aad6f19fc1122aaf320cbe3dc83", + "sha256:ccb42ca0a4a46232d716779421bbebbcad23c08d37c980f02cc3a6bd115ad277", + "sha256:ce6f2b66799971cbae5d6547acefa7231458289e0ad481d0be0740535da38d8b", + "sha256:d36b8fffe8b248a1b961c86fbdfa0129dfce878731d169ede7fa2631447331be", + "sha256:d3dd5523ed258ad58fed7e364c92a9360d1af8a9371e0822bd0146bdf017ef4c", + "sha256:d416f2088ac8f12daacffbc2e8918ef4d6be8568e9d7155c83b7cebed49d2322", + "sha256:d4fafc2eb5d83f4647331267808c7e0c5722c25a729a614dc2b90479cafa78bd", + "sha256:d5c8b17f6e8f29138678834cf8518049e740385eb2dbf736e8f07fc6587ec682", + "sha256:d9270fbf038bf34ffca4855bcda6e082e2c7f906b9eb8d9a8ce82691166060f7", + "sha256:dcc37d9d708784726fafc9c5e1232de655a009dbf97946f117aefa38d5985a0f", + "sha256:ddbb2b386128d8eca92bd9ca74e80f73fe263bcca7aa419f5b4cbc1661e19741", + "sha256:e1e5d0a25aea8b691a00d6b54b28ac514c8cc0d8646d05f7ca6cb64b97358250", + "sha256:e5c88b2f13bcf55fee78ea83567b9fe079ba1a4bef8b35c376043440040f7edb", + "sha256:e7eca8b89e56fb8c6c26dd3e09bd41b24789022acf1cf13358e96f1cafd8cae3", + "sha256:e8746ce968be22a8a1801bf4a23e565f9687088580c3ed07af5846580dd97f76", + "sha256:ec7248673ffc7104b54e4957cee38b2f3075a13442348c8d651777bf41aa45ee", + "sha256:ecb6c88d7946166d783a635efc89f9a1ff11c33d680a20df9657b6902a1d133b", + "sha256:ef3b048822dca6d231d8a8ba21069844ae38f5d83889b9b690bf17d2acc7d099", + "sha256:f133d05aaf623519f45e16ab77526e1e70d4e1308e084c2fb4cedb1a0c764bbb", + "sha256:f3292d384537b9918010769b82ab3e79fca8b23d74f56fc69a679106a3e2c2cf", + "sha256:f774841bb0e8588505002962c02da420bcfb4c5056e87a139c6e45e745c0e2e2", + "sha256:f9499c70c19ff0fbe1007043acb5ad15c1dec7d8e84ab429bca8c87138e8f85c", + "sha256:f99de52b8fbdb2a8f5301ae5fc0f9e6b3ba30d1d5fc0421956967edcc6914242", + "sha256:fa25a620eed2a419acc2cf10135b995f8f0ce78ad00534d729aa761e4adcef8a", + "sha256:fbf558551cf415586e91160d69ca6416f3fce0b86175b64e4293644a7416b81b", + "sha256:fc82269d24860cfa859b676d18850cbb8e312dcd7eada09e7d5b007e2f3d9eb1", + "sha256:ff832cce719edd11266ca32bc74a626b814fff236824aa1aeaad399b69fe6eae" ], - "markers": "python_version >= '3.6'", - "version": "==25.1.1" + "markers": "python_version >= '3.7'", + "version": "==26.1.0" }, "qtconsole": { "hashes": [ - "sha256:6b6bcf8f834c6df1579a3e6623c8531b85d3e723997cee3a1156296df14716c8", - "sha256:ea8b4a07d7dc915a1b1238fbfe2c9aea570640402557b64615e09a4bc60df47c" + "sha256:42d745f3d05d36240244a04e1e1ec2a86d5d9b6edb16dbdef582ccb629e87e0b", + "sha256:6b5fb11274b297463706af84dcbbd5c92273b1f619e6d25d08874b0a88516989" ], "markers": "python_version >= '3.8'", - "version": "==5.5.0" + "version": "==5.5.2" }, "qtpy": { "hashes": [ @@ -1477,19 +1578,19 @@ }, "referencing": { "hashes": [ - "sha256:381b11e53dd93babb55696c71cf42aef2d36b8a150c49bf0bc301e36d536c882", - "sha256:cc28f2c88fbe7b961a7817a0abc034c09a1e36358f82fedb4ffdf29a25398863" + "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", + "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de" ], "markers": "python_version >= '3.8'", - "version": "==0.31.0" + "version": "==0.35.1" }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.3" }, "rfc3339-validator": { "hashes": [ @@ -1509,116 +1610,128 @@ }, "rpds-py": { "hashes": [ - "sha256:0525847f83f506aa1e28eb2057b696fe38217e12931c8b1b02198cfe6975e142", - "sha256:05942656cb2cb4989cd50ced52df16be94d344eae5097e8583966a1d27da73a5", - "sha256:0831d3ecdea22e4559cc1793f22e77067c9d8c451d55ae6a75bf1d116a8e7f42", - "sha256:0853da3d5e9bc6a07b2486054a410b7b03f34046c123c6561b535bb48cc509e1", - "sha256:08e6e7ff286254016b945e1ab632ee843e43d45e40683b66dd12b73791366dd1", - "sha256:0a38612d07a36138507d69646c470aedbfe2b75b43a4643f7bd8e51e52779624", - "sha256:0bedd91ae1dd142a4dc15970ed2c729ff6c73f33a40fa84ed0cdbf55de87c777", - "sha256:0c5441b7626c29dbd54a3f6f3713ec8e956b009f419ffdaaa3c80eaf98ddb523", - "sha256:0e9e976e0dbed4f51c56db10831c9623d0fd67aac02853fe5476262e5a22acb7", - "sha256:0fadfdda275c838cba5102c7f90a20f2abd7727bf8f4a2b654a5b617529c5c18", - "sha256:1096ca0bf2d3426cbe79d4ccc91dc5aaa73629b08ea2d8467375fad8447ce11a", - "sha256:171d9a159f1b2f42a42a64a985e4ba46fc7268c78299272ceba970743a67ee50", - "sha256:188912b22b6c8225f4c4ffa020a2baa6ad8fabb3c141a12dbe6edbb34e7f1425", - "sha256:1b4cf9ab9a0ae0cb122685209806d3f1dcb63b9fccdf1424fb42a129dc8c2faa", - "sha256:1e04581c6117ad9479b6cfae313e212fe0dfa226ac727755f0d539cd54792963", - "sha256:1fa73ed22c40a1bec98d7c93b5659cd35abcfa5a0a95ce876b91adbda170537c", - "sha256:2124f9e645a94ab7c853bc0a3644e0ca8ffbe5bb2d72db49aef8f9ec1c285733", - "sha256:240687b5be0f91fbde4936a329c9b7589d9259742766f74de575e1b2046575e4", - "sha256:25740fb56e8bd37692ed380e15ec734be44d7c71974d8993f452b4527814601e", - "sha256:27ccc93c7457ef890b0dd31564d2a05e1aca330623c942b7e818e9e7c2669ee4", - "sha256:281c8b219d4f4b3581b918b816764098d04964915b2f272d1476654143801aa2", - "sha256:2d34a5450a402b00d20aeb7632489ffa2556ca7b26f4a63c35f6fccae1977427", - "sha256:301bd744a1adaa2f6a5e06c98f1ac2b6f8dc31a5c23b838f862d65e32fca0d4b", - "sha256:30e5ce9f501fb1f970e4a59098028cf20676dee64fc496d55c33e04bbbee097d", - "sha256:33ab498f9ac30598b6406e2be1b45fd231195b83d948ebd4bd77f337cb6a2bff", - "sha256:35585a8cb5917161f42c2104567bb83a1d96194095fc54a543113ed5df9fa436", - "sha256:389c0e38358fdc4e38e9995e7291269a3aead7acfcf8942010ee7bc5baee091c", - "sha256:3acadbab8b59f63b87b518e09c4c64b142e7286b9ca7a208107d6f9f4c393c5c", - "sha256:3b7a64d43e2a1fa2dd46b678e00cabd9a49ebb123b339ce799204c44a593ae1c", - "sha256:3c8c0226c71bd0ce9892eaf6afa77ae8f43a3d9313124a03df0b389c01f832de", - "sha256:429349a510da82c85431f0f3e66212d83efe9fd2850f50f339341b6532c62fe4", - "sha256:466030a42724780794dea71eb32db83cc51214d66ab3fb3156edd88b9c8f0d78", - "sha256:47aeceb4363851d17f63069318ba5721ae695d9da55d599b4d6fb31508595278", - "sha256:48aa98987d54a46e13e6954880056c204700c65616af4395d1f0639eba11764b", - "sha256:4b2416ed743ec5debcf61e1242e012652a4348de14ecc7df3512da072b074440", - "sha256:4d0a675a7acbbc16179188d8c6d0afb8628604fc1241faf41007255957335a0b", - "sha256:4eb74d44776b0fb0782560ea84d986dffec8ddd94947f383eba2284b0f32e35e", - "sha256:4f8a1d990dc198a6c68ec3d9a637ba1ce489b38cbfb65440a27901afbc5df575", - "sha256:513ccbf7420c30e283c25c82d5a8f439d625a838d3ba69e79a110c260c46813f", - "sha256:5210a0018c7e09c75fa788648617ebba861ae242944111d3079034e14498223f", - "sha256:54cdfcda59251b9c2f87a05d038c2ae02121219a04d4a1e6fc345794295bdc07", - "sha256:56dd500411d03c5e9927a1eb55621e906837a83b02350a9dc401247d0353717c", - "sha256:57ec6baec231bb19bb5fd5fc7bae21231860a1605174b11585660236627e390e", - "sha256:5f1519b080d8ce0a814f17ad9fb49fb3a1d4d7ce5891f5c85fc38631ca3a8dc4", - "sha256:6174d6ad6b58a6bcf67afbbf1723420a53d06c4b89f4c50763d6fa0a6ac9afd2", - "sha256:68172622a5a57deb079a2c78511c40f91193548e8ab342c31e8cb0764d362459", - "sha256:6915fc9fa6b3ec3569566832e1bb03bd801c12cea030200e68663b9a87974e76", - "sha256:6b75b912a0baa033350367a8a07a8b2d44fd5b90c890bfbd063a8a5f945f644b", - "sha256:6f5dcb658d597410bb7c967c1d24eaf9377b0d621358cbe9d2ff804e5dd12e81", - "sha256:6f8d7fe73d1816eeb5378409adc658f9525ecbfaf9e1ede1e2d67a338b0c7348", - "sha256:7036316cc26b93e401cedd781a579be606dad174829e6ad9e9c5a0da6e036f80", - "sha256:7188ddc1a8887194f984fa4110d5a3d5b9b5cd35f6bafdff1b649049cbc0ce29", - "sha256:761531076df51309075133a6bc1db02d98ec7f66e22b064b1d513bc909f29743", - "sha256:7979d90ee2190d000129598c2b0c82f13053dba432b94e45e68253b09bb1f0f6", - "sha256:8015835494b21aa7abd3b43fdea0614ee35ef6b03db7ecba9beb58eadf01c24f", - "sha256:81c4d1a3a564775c44732b94135d06e33417e829ff25226c164664f4a1046213", - "sha256:81cf9d306c04df1b45971c13167dc3bad625808aa01281d55f3cf852dde0e206", - "sha256:88857060b690a57d2ea8569bca58758143c8faa4639fb17d745ce60ff84c867e", - "sha256:8c567c664fc2f44130a20edac73e0a867f8e012bf7370276f15c6adc3586c37c", - "sha256:91bd2b7cf0f4d252eec8b7046fa6a43cee17e8acdfc00eaa8b3dbf2f9a59d061", - "sha256:9620650c364c01ed5b497dcae7c3d4b948daeae6e1883ae185fef1c927b6b534", - "sha256:9b007c2444705a2dc4a525964fd4dd28c3320b19b3410da6517cab28716f27d3", - "sha256:9bf9acce44e967a5103fcd820fc7580c7b0ab8583eec4e2051aec560f7b31a63", - "sha256:a239303acb0315091d54c7ff36712dba24554993b9a93941cf301391d8a997ee", - "sha256:a2baa6be130e8a00b6cbb9f18a33611ec150b4537f8563bddadb54c1b74b8193", - "sha256:a54917b7e9cd3a67e429a630e237a90b096e0ba18897bfb99ee8bd1068a5fea0", - "sha256:a689e1ded7137552bea36305a7a16ad2b40be511740b80748d3140614993db98", - "sha256:a952ae3eb460c6712388ac2ec706d24b0e651b9396d90c9a9e0a69eb27737fdc", - "sha256:aa32205358a76bf578854bf31698a86dc8b2cb591fd1d79a833283f4a403f04b", - "sha256:b2287c09482949e0ca0c0eb68b2aca6cf57f8af8c6dfd29dcd3bc45f17b57978", - "sha256:b6b0e17d39d21698185097652c611f9cf30f7c56ccec189789920e3e7f1cee56", - "sha256:b710bf7e7ae61957d5c4026b486be593ed3ec3dca3e5be15e0f6d8cf5d0a4990", - "sha256:b8e11715178f3608874508f08e990d3771e0b8c66c73eb4e183038d600a9b274", - "sha256:b92aafcfab3d41580d54aca35a8057341f1cfc7c9af9e8bdfc652f83a20ced31", - "sha256:bec29b801b4adbf388314c0d050e851d53762ab424af22657021ce4b6eb41543", - "sha256:c694bee70ece3b232df4678448fdda245fd3b1bb4ba481fb6cd20e13bb784c46", - "sha256:c6b52b7028b547866c2413f614ee306c2d4eafdd444b1ff656bf3295bf1484aa", - "sha256:cb41ad20064e18a900dd427d7cf41cfaec83bcd1184001f3d91a1f76b3fcea4e", - "sha256:cd316dbcc74c76266ba94eb021b0cc090b97cca122f50bd7a845f587ff4bf03f", - "sha256:ced40cdbb6dd47a032725a038896cceae9ce267d340f59508b23537f05455431", - "sha256:d1c562a9bb72244fa767d1c1ab55ca1d92dd5f7c4d77878fee5483a22ffac808", - "sha256:d389ff1e95b6e46ebedccf7fd1fadd10559add595ac6a7c2ea730268325f832c", - "sha256:d56b1cd606ba4cedd64bb43479d56580e147c6ef3f5d1c5e64203a1adab784a2", - "sha256:d72a4315514e5a0b9837a086cb433b004eea630afb0cc129de76d77654a9606f", - "sha256:d9e7f29c00577aff6b318681e730a519b235af292732a149337f6aaa4d1c5e31", - "sha256:dbc25baa6abb205766fb8606f8263b02c3503a55957fcb4576a6bb0a59d37d10", - "sha256:e57919c32ee295a2fca458bb73e4b20b05c115627f96f95a10f9f5acbd61172d", - "sha256:e5bbe011a2cea9060fef1bb3d668a2fd8432b8888e6d92e74c9c794d3c101595", - "sha256:e6aea5c0eb5b0faf52c7b5c4a47c8bb64437173be97227c819ffa31801fa4e34", - "sha256:e888be685fa42d8b8a3d3911d5604d14db87538aa7d0b29b1a7ea80d354c732d", - "sha256:eebaf8c76c39604d52852366249ab807fe6f7a3ffb0dd5484b9944917244cdbe", - "sha256:efbe0b5e0fd078ed7b005faa0170da4f72666360f66f0bb2d7f73526ecfd99f9", - "sha256:efddca2d02254a52078c35cadad34762adbae3ff01c6b0c7787b59d038b63e0d", - "sha256:f05450fa1cd7c525c0b9d1a7916e595d3041ac0afbed2ff6926e5afb6a781b7f", - "sha256:f12d69d568f5647ec503b64932874dade5a20255736c89936bf690951a5e79f5", - "sha256:f45321224144c25a62052035ce96cbcf264667bcb0d81823b1bbc22c4addd194", - "sha256:f62581d7e884dd01ee1707b7c21148f61f2febb7de092ae2f108743fcbef5985", - "sha256:f8832a4f83d4782a8f5a7b831c47e8ffe164e43c2c148c8160ed9a6d630bc02a", - "sha256:fa35ad36440aaf1ac8332b4a4a433d4acd28f1613f0d480995f5cfd3580e90b7" - ], - "markers": "python_version >= '3.8'", - "version": "==0.12.0" + "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", + "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", + "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5", + "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", + "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", + "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", + "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29", + "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", + "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b", + "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", + "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", + "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", + "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", + "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a", + "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", + "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", + "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03", + "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", + "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22", + "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e", + "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", + "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", + "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752", + "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", + "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253", + "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", + "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", + "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5", + "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", + "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7", + "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", + "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", + "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", + "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", + "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec", + "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", + "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921", + "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", + "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074", + "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580", + "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", + "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", + "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", + "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", + "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", + "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", + "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", + "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", + "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789", + "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", + "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", + "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c", + "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232", + "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", + "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c", + "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", + "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", + "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", + "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751", + "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", + "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda", + "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", + "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", + "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", + "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8", + "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", + "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", + "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1", + "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2", + "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", + "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", + "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965", + "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", + "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", + "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b", + "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", + "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", + "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", + "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de", + "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", + "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", + "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", + "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", + "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", + "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1", + "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", + "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", + "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", + "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364", + "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", + "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", + "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420", + "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5", + "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24", + "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c", + "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", + "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f", + "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e", + "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab", + "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08", + "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", + "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", + "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8" + ], + "markers": "python_version >= '3.8'", + "version": "==0.20.0" }, "send2trash": { "hashes": [ - "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679", - "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312" + "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", + "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.8.2" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==1.8.3" + }, + "setuptools": { + "hashes": [ + "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1", + "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec" + ], + "markers": "python_version >= '3.8'", + "version": "==72.1.0" }, "six": { "hashes": [ @@ -1630,11 +1743,11 @@ }, "sniffio": { "hashes": [ - "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", - "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" + "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", + "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc" ], "markers": "python_version >= '3.7'", - "version": "==1.3.0" + "version": "==1.3.1" }, "soupsieve": { "hashes": [ @@ -1653,19 +1766,19 @@ }, "terminado": { "hashes": [ - "sha256:1ea08a89b835dd1b8c0c900d92848147cef2537243361b2e3f4dc15df9b6fded", - "sha256:87b0d96642d0fe5f5abd7783857b9cab167f221a39ff98e3b9619a788a3c0f2e" + "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", + "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e" ], "markers": "python_version >= '3.8'", - "version": "==0.18.0" + "version": "==0.18.1" }, "tinycss2": { "hashes": [ - "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847", - "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627" + "sha256:152f9acabd296a8375fbca5b84c961ff95971fcfc32e79550c8df8e29118c54d", + "sha256:54a8dbdffb334d536851be0226030e9505965bb2f30f21a4a82c55fb2a80fae7" ], - "markers": "python_version >= '3.7'", - "version": "==1.2.1" + "markers": "python_version >= '3.8'", + "version": "==1.3.0" }, "tomli": { "hashes": [ @@ -1677,67 +1790,68 @@ }, "tornado": { "hashes": [ - "sha256:1bd19ca6c16882e4d37368e0152f99c099bad93e0950ce55e71daed74045908f", - "sha256:22d3c2fa10b5793da13c807e6fc38ff49a4f6e1e3868b0a6f4164768bb8e20f5", - "sha256:502fba735c84450974fec147340016ad928d29f1e91f49be168c0a4c18181e1d", - "sha256:65ceca9500383fbdf33a98c0087cb975b2ef3bfb874cb35b8de8740cf7f41bd3", - "sha256:71a8db65160a3c55d61839b7302a9a400074c9c753040455494e2af74e2501f2", - "sha256:7ac51f42808cca9b3613f51ffe2a965c8525cb1b00b7b2d56828b8045354f76a", - "sha256:7d01abc57ea0dbb51ddfed477dfe22719d376119844e33c661d873bf9c0e4a16", - "sha256:805d507b1f588320c26f7f097108eb4023bbaa984d63176d1652e184ba24270a", - "sha256:9dc4444c0defcd3929d5c1eb5706cbe1b116e762ff3e0deca8b715d14bf6ec17", - "sha256:ceb917a50cd35882b57600709dd5421a418c29ddc852da8bcdab1f0db33406b0", - "sha256:e7d8db41c0181c80d76c982aacc442c0783a2c54d6400fe028954201a2e032fe" + "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", + "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", + "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", + "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", + "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", + "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", + "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", + "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", + "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", + "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", + "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4" ], "markers": "python_version >= '3.8'", - "version": "==6.3.3" + "version": "==6.4.1" }, "tqdm": { "hashes": [ - "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386", - "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7" + "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", + "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad" ], "markers": "python_version >= '3.7'", - "version": "==4.66.1" + "version": "==4.66.5" }, "traitlets": { "hashes": [ - "sha256:9b232b9430c8f57288c1024b34a8f0251ddcc47268927367a0dd3eeaca40deb5", - "sha256:baf991e61542da48fe8aef8b779a9ea0aa38d8a54166ee250d5af5ecf4486619" + "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", + "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" ], "markers": "python_version >= '3.8'", - "version": "==5.13.0" + "version": "==5.14.3" }, "types-python-dateutil": { "hashes": [ - "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b", - "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9" + "sha256:5d2f2e240b86905e40944dd787db6da9263f0deabef1076ddaed797351ec0202", + "sha256:6b8cb66d960771ce5ff974e9dd45e38facb81718cc1e208b10b1baccbfdbee3b" ], - "version": "==2.8.19.14" + "markers": "python_version >= '3.8'", + "version": "==2.9.0.20240316" }, "typing-extensions": { "hashes": [ - "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0", - "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef" + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" ], "markers": "python_version >= '3.8'", - "version": "==4.8.0" + "version": "==4.12.2" }, "tzdata": { "hashes": [ - "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a", - "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda" + "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd", + "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252" ], "markers": "python_version >= '2'", - "version": "==2023.3" + "version": "==2024.1" }, "uc-micro-py": { "hashes": [ - "sha256:30ae2ac9c49f39ac6dce743bd187fcd2b574b16ca095fa74cd9396795c954c54", - "sha256:8c9110c309db9d9e87302e2f4ad2c3152770930d88ab385cd544e7a7e75f3de0" + "sha256:d321b92cff673ec58027c04015fcaa8bb1e005478643ff4a500882eaab88c48a", + "sha256:db1dffff340817673d7b466ec86114a9dc0e9d4d9b5ba229d9d60e5c12600cd5" ], "markers": "python_version >= '3.7'", - "version": "==1.0.2" + "version": "==1.0.3" }, "uri-template": { "hashes": [ @@ -1748,25 +1862,25 @@ }, "urllib3": { "hashes": [ - "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", - "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" + "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", + "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" ], "markers": "python_version >= '3.8'", - "version": "==2.1.0" + "version": "==2.2.2" }, "wcwidth": { "hashes": [ - "sha256:390c7454101092a6a5e43baad8f83de615463af459201709556b6e4b1c861f97", - "sha256:aec5179002dd0f0d40c456026e74a729661c9d468e1ed64405e3a6c2176ca36f" + "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" ], - "version": "==0.2.10" + "version": "==0.2.13" }, "webcolors": { "hashes": [ - "sha256:29bc7e8752c0a1bd4a1f03c14d6e6a72e93d82193738fa860cbff59d0fcc11bf", - "sha256:c225b674c83fa923be93d235330ce0300373d02885cef23238813b0d5668304a" + "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b", + "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1" ], - "version": "==1.13" + "version": "==24.6.0" }, "webencodings": { "hashes": [ @@ -1777,11 +1891,11 @@ }, "websocket-client": { "hashes": [ - "sha256:084072e0a7f5f347ef2ac3d8698a5e0b4ffbfcab607628cadabc650fc9a83a24", - "sha256:b3324019b3c28572086c4a319f91d1dcd44e6e11cd340232978c684a7650d0df" + "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", + "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da" ], "markers": "python_version >= '3.8'", - "version": "==1.6.4" + "version": "==1.8.0" }, "websockets": { "hashes": [ @@ -1859,43 +1973,73 @@ "sha256:ffefa1374cd508d633646d51a8e9277763a9b78ae71324183693959cf94635a7" ], "index": "pypi", - "markers": "python_version >= '3.8'", "version": "==12.0" }, "widgetsnbextension": { "hashes": [ - "sha256:3c1f5e46dc1166dfd40a42d685e6a51396fd34ff878742a3e47c6f0cc4a2a385", - "sha256:91452ca8445beb805792f206e560c1769284267a30ceb1cec9f5bcc887d15175" + "sha256:55d4d6949d100e0d08b94948a42efc3ed6dfdc0e9468b2c4b128c9a2ce3a7a36", + "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474" ], "markers": "python_version >= '3.7'", - "version": "==4.0.9" + "version": "==4.0.11" }, "xyzservices": { "hashes": [ - "sha256:091229269043bc8258042edbedad4fcb44684b0473ede027b5672ad40dc9fa02", - "sha256:6a4c38d3a9f89d3e77153eff9414b36a8ee0850c9e8b85796fd1b2a85b8dfd68" + "sha256:58c1bdab4257d2551b9ef91cd48571f77b7c4d2bc45bf5e3c05ac97b3a4d7282", + "sha256:fecb2508f0f2b71c819aecf5df2c03cef001c56a4b49302e640f3b34710d25e4" + ], + "markers": "python_version >= '3.8'", + "version": "==2024.6.0" + }, + "zipp": { + "hashes": [ + "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19", + "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c" ], "markers": "python_version >= '3.8'", - "version": "==2023.10.1" + "version": "==3.19.2" } }, "develop": { + "alabaster": { + "hashes": [ + "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", + "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92" + ], + "markers": "python_version >= '3.9'", + "version": "==0.7.16" + }, + "babel": { + "hashes": [ + "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", + "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" + ], + "markers": "python_version >= '3.8'", + "version": "==2.16.0" + }, + "backports.tarfile": { + "hashes": [ + "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", + "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991" + ], + "markers": "python_version < '3.12'", + "version": "==1.2.0" + }, "build": { "hashes": [ - "sha256:538aab1b64f9828977f84bc63ae570b060a8ed1be419e7870b8b4fc5e6ea553b", - "sha256:589bf99a67df7c9cf07ec0ac0e5e2ea5d4b37ac63301c4986d1acb126aa83f8f" + "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d", + "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==1.0.3" + "version": "==1.2.1" }, "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2024.7.4" }, "charset-normalizer": { "hashes": [ @@ -2003,35 +2147,67 @@ }, "idna": { "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], "markers": "python_version >= '3.5'", - "version": "==3.4" + "version": "==3.7" + }, + "imagesize": { + "hashes": [ + "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", + "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.4.1" }, "importlib-metadata": { "hashes": [ - "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", - "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743" + "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369", + "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d" ], - "markers": "python_version >= '3.8'", - "version": "==6.8.0" + "markers": "python_version < '3.10'", + "version": "==8.2.0" }, "jaraco.classes": { "hashes": [ - "sha256:10afa92b6743f25c0cf5f37c6bb6e18e2c5bb84a16527ccfc0040ea377e7aaeb", - "sha256:c063dd08e89217cee02c8d5e5ec560f2c8ce6cdc2fcdc2e68f7b2e5547ed3621" + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266", + "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2" ], "markers": "python_version >= '3.8'", - "version": "==3.3.0" + "version": "==5.3.0" + }, + "jaraco.functools": { + "hashes": [ + "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5", + "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3" + ], + "markers": "python_version >= '3.8'", + "version": "==4.0.2" + }, + "jinja2": { + "hashes": [ + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.4" }, "keyring": { "hashes": [ - "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836", - "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25" + "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef", + "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae" ], "markers": "python_version >= '3.8'", - "version": "==24.3.0" + "version": "==25.3.0" }, "markdown-it-py": { "hashes": [ @@ -2041,6 +2217,72 @@ "markers": "python_version >= '3.8'", "version": "==3.0.0" }, + "markupsafe": { + "hashes": [ + "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf", + "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff", + "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", + "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3", + "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532", + "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f", + "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", + "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df", + "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4", + "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", + "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", + "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", + "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8", + "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371", + "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2", + "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465", + "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52", + "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6", + "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", + "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad", + "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", + "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0", + "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029", + "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", + "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a", + "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", + "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5", + "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", + "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf", + "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9", + "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", + "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", + "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3", + "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", + "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46", + "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc", + "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a", + "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", + "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900", + "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", + "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea", + "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", + "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5", + "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e", + "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", + "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f", + "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50", + "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", + "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", + "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4", + "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff", + "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2", + "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46", + "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", + "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf", + "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", + "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5", + "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab", + "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd", + "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.5" + }, "mdurl": { "hashes": [ "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", @@ -2051,80 +2293,88 @@ }, "more-itertools": { "hashes": [ - "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a", - "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6" + "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27", + "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923" ], "markers": "python_version >= '3.8'", - "version": "==10.1.0" + "version": "==10.4.0" }, "nh3": { "hashes": [ - "sha256:116c9515937f94f0057ef50ebcbcc10600860065953ba56f14473ff706371873", - "sha256:18415df36db9b001f71a42a3a5395db79cf23d556996090d293764436e98e8ad", - "sha256:203cac86e313cf6486704d0ec620a992c8bc164c86d3a4fd3d761dd552d839b5", - "sha256:2b0be5c792bd43d0abef8ca39dd8acb3c0611052ce466d0401d51ea0d9aa7525", - "sha256:377aaf6a9e7c63962f367158d808c6a1344e2b4f83d071c43fbd631b75c4f0b2", - "sha256:525846c56c2bcd376f5eaee76063ebf33cf1e620c1498b2a40107f60cfc6054e", - "sha256:5529a3bf99402c34056576d80ae5547123f1078da76aa99e8ed79e44fa67282d", - "sha256:7771d43222b639a4cd9e341f870cee336b9d886de1ad9bec8dddab22fe1de450", - "sha256:88c753efbcdfc2644a5012938c6b9753f1c64a5723a67f0301ca43e7b85dcf0e", - "sha256:93a943cfd3e33bd03f77b97baa11990148687877b74193bf777956b67054dcc6", - "sha256:9be2f68fb9a40d8440cbf34cbf40758aa7f6093160bfc7fb018cce8e424f0c3a", - "sha256:a0c509894fd4dccdff557068e5074999ae3b75f4c5a2d6fb5415e782e25679c4", - "sha256:ac8056e937f264995a82bf0053ca898a1cb1c9efc7cd68fa07fe0060734df7e4", - "sha256:aed56a86daa43966dd790ba86d4b810b219f75b4bb737461b6886ce2bde38fd6", - "sha256:e8986f1dd3221d1e741fda0a12eaa4a273f1d80a35e31a1ffe579e7c621d069e", - "sha256:f99212a81c62b5f22f9e7c3e347aa00491114a5647e1f13bbebd79c3e5f08d75" - ], - "version": "==0.2.14" + "sha256:0411beb0589eacb6734f28d5497ca2ed379eafab8ad8c84b31bb5c34072b7164", + "sha256:14c5a72e9fe82aea5fe3072116ad4661af5cf8e8ff8fc5ad3450f123e4925e86", + "sha256:19aaba96e0f795bd0a6c56291495ff59364f4300d4a39b29a0abc9cb3774a84b", + "sha256:34c03fa78e328c691f982b7c03d4423bdfd7da69cd707fe572f544cf74ac23ad", + "sha256:36c95d4b70530b320b365659bb5034341316e6a9b30f0b25fa9c9eff4c27a204", + "sha256:3a157ab149e591bb638a55c8c6bcb8cdb559c8b12c13a8affaba6cedfe51713a", + "sha256:42c64511469005058cd17cc1537578eac40ae9f7200bedcfd1fc1a05f4f8c200", + "sha256:5f36b271dae35c465ef5e9090e1fdaba4a60a56f0bb0ba03e0932a66f28b9189", + "sha256:6955369e4d9f48f41e3f238a9e60f9410645db7e07435e62c6a9ea6135a4907f", + "sha256:7b7c2a3c9eb1a827d42539aa64091640bd275b81e097cd1d8d82ef91ffa2e811", + "sha256:8ce0f819d2f1933953fca255db2471ad58184a60508f03e6285e5114b6254844", + "sha256:94a166927e53972a9698af9542ace4e38b9de50c34352b962f4d9a7d4c927af4", + "sha256:a7f1b5b2c15866f2db413a3649a8fe4fd7b428ae58be2c0f6bca5eefd53ca2be", + "sha256:c8b3a1cebcba9b3669ed1a84cc65bf005728d2f0bc1ed2a6594a992e817f3a50", + "sha256:de3ceed6e661954871d6cd78b410213bdcb136f79aafe22aa7182e028b8c7307", + "sha256:f0eca9ca8628dbb4e916ae2491d72957fdd35f7a5d326b7032a345f111ac07fe" + ], + "version": "==0.2.18" }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", + "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" ], - "markers": "python_version >= '3.7'", - "version": "==23.2" + "markers": "python_version >= '3.8'", + "version": "==24.1" + }, + "pip-chill": { + "hashes": [ + "sha256:42c3b888efde0b3dc5d5307b92fae5fb67695dd9c29c9d31891b9505dd8b735a", + "sha256:452a38edbcdfc333301c438c26ba00a0762d2034fe26a235d8587134453ccdb1" + ], + "index": "pypi", + "version": "==1.0.3" }, "pkginfo": { "hashes": [ - "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546", - "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046" + "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097" ], "markers": "python_version >= '3.6'", - "version": "==1.9.6" + "version": "==1.10.0" }, "pygments": { "hashes": [ - "sha256:13fc09fa63bc8d8671a6d247e1eb303c4b343eaee81d861f3404db2935653692", - "sha256:1daff0494820c69bc8941e407aa20f577374ee88364ee10a98fdbe0aece96e29" + "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", + "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a" ], - "markers": "python_version >= '3.7'", - "version": "==2.16.1" + "markers": "python_version >= '3.8'", + "version": "==2.18.0" }, "pyproject-hooks": { "hashes": [ - "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8", - "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5" + "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965", + "sha256:7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2" ], "markers": "python_version >= '3.7'", - "version": "==1.0.0" + "version": "==1.1.0" }, "readme-renderer": { "hashes": [ - "sha256:13d039515c1f24de668e2c93f2e877b9dbe6c6c32328b90a40a49d8b2b85f36d", - "sha256:2d55489f83be4992fe4454939d1a051c33edbab778e82761d060c9fc6b308cd1" + "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311", + "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9" ], "markers": "python_version >= '3.8'", - "version": "==42.0" + "version": "==43.0" }, "requests": { "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" ], - "markers": "python_version >= '3.7'", - "version": "==2.31.0" + "markers": "python_version >= '3.8'", + "version": "==2.32.3" }, "requests-toolbelt": { "hashes": [ @@ -2144,11 +2394,90 @@ }, "rich": { "hashes": [ - "sha256:2b38e2fe9ca72c9a00170a1a2d20c63c790d0e10ef1fe35eba76e1e7b1d7d245", - "sha256:5c14d22737e6d5084ef4771b62d5d4363165b403455a30a1c8ca39dc7b644bef" + "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", + "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" ], "markers": "python_full_version >= '3.7.0'", - "version": "==13.6.0" + "version": "==13.7.1" + }, + "snowballstemmer": { + "hashes": [ + "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", + "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" + ], + "version": "==2.2.0" + }, + "sphinx": { + "hashes": [ + "sha256:242f92a7ea7e6c5b406fdc2615413890ba9f699114a9c09192d7dfead2ee9cfe", + "sha256:c2419e2135d11f1951cd994d6eb18a1835bd8fdd8429f9ca375dc1f3281bd239" + ], + "index": "pypi", + "version": "==7.4.7" + }, + "sphinx-rtd-theme": { + "hashes": [ + "sha256:bd5d7b80622406762073a04ef8fadc5f9151261563d47027de09910ce03afe6b", + "sha256:ec93d0856dc280cf3aee9a4c9807c60e027c7f7b461b77aeffed682e68f0e586" + ], + "index": "pypi", + "version": "==2.0.0" + }, + "sphinxcontrib-applehelp": { + "hashes": [ + "sha256:2f29ef331735ce958efa4734873f084941970894c6090408b079c61b2e1c06d1", + "sha256:4cd3f0ec4ac5dd9c17ec65e9ab272c9b867ea77425228e68ecf08d6b28ddbdb5" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "sphinxcontrib-devhelp": { + "hashes": [ + "sha256:411f5d96d445d1d73bb5d52133377b4248ec79db5c793ce7dbe59e074b4dd1ad", + "sha256:aefb8b83854e4b0998877524d1029fd3e6879210422ee3780459e28a1f03a8a2" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "sphinxcontrib-htmlhelp": { + "hashes": [ + "sha256:166759820b47002d22914d64a075ce08f4c46818e17cfc9470a9786b759b19f8", + "sha256:c9e2916ace8aad64cc13a0d233ee22317f2b9025b9cf3295249fa985cc7082e9" + ], + "markers": "python_version >= '3.9'", + "version": "==2.1.0" + }, + "sphinxcontrib-jquery": { + "hashes": [ + "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a", + "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae" + ], + "markers": "python_version >= '2.7'", + "version": "==4.1" + }, + "sphinxcontrib-jsmath": { + "hashes": [ + "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", + "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "sphinxcontrib-qthelp": { + "hashes": [ + "sha256:4fe7d0ac8fc171045be623aba3e2a8f613f8682731f9153bb2e40ece16b9bbab", + "sha256:b18a828cdba941ccd6ee8445dbe72ffa3ef8cbe7505d8cd1fa0d42d3f2d5f3eb" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" + }, + "sphinxcontrib-serializinghtml": { + "hashes": [ + "sha256:6e2cb0eef194e10c27ec0023bfeb25badbbb5868244cf5bc5bdc04e4464bf331", + "sha256:e9d912827f872c029017a53f0ef2180b327c3f7fd23c87229f7a8e8b70031d4d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.0.0" }, "tomli": { "hashes": [ @@ -2160,28 +2489,27 @@ }, "twine": { "hashes": [ - "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8", - "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8" + "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==4.0.2" + "version": "==5.1.1" }, "urllib3": { "hashes": [ - "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3", - "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54" + "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472", + "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168" ], "markers": "python_version >= '3.8'", - "version": "==2.1.0" + "version": "==2.2.2" }, "zipp": { "hashes": [ - "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", - "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" + "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19", + "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c" ], "markers": "python_version >= '3.8'", - "version": "==3.17.0" + "version": "==3.19.2" } } } diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..8f01f6b --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,3 @@ +footer { + display: none !important; +} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..9e1f15f --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,63 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import os +import sys +import re +sys.path.insert(0, os.path.abspath('../pybela')) + +author = 'Teresa Pelinski' +copyright = '2024' +def get_version_from_setup_py(): + version_pattern = re.compile(r"version=['\"]([^'\"]+)['\"]") + with open('../setup.py', 'r') as f: + setup_py_content = f.read() + match = version_pattern.search(setup_py_content) + if match: + return match.group(1) + raise RuntimeError("Unable to find version string in setup.py") + +release = get_version_from_setup_py() +project = f'pybela {release}' + +# -- General configuration --------------------------------------------------- +extensions = [ + 'sphinx.ext.napoleon', 'sphinx.ext.viewcode', 'sphinx_rtd_theme'] +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '../pybela/utils.py'] + + +# -- Options for HTML output ------------------------------------------------- +html_theme = 'sphinx_rtd_theme' +html_css_files = [ + 'custom.css', +] +html_static_path = ['_static'] +html_css_files = ['custom.css'] +html_show_sphinx = False +html_show_sourcelink = False +html_sidebars = { + '**': ['globaltoc.html', 'searchbox.html'] +} +html_theme_options = { + 'collapse_navigation': False, + 'sticky_navigation': True, + 'navigation_depth': 4, + 'titles_only': False, + 'display_version': True, + 'prev_next_buttons_location': 'None', +} + +# remove title from readme file to avoid duplication +file_path = 'readme.rst' + +with open(file_path, 'r+') as file: + lines = file.readlines()[2:] # Read lines and skip the first two + file.seek(0) # Move the cursor to the beginning of the file + file.writelines(lines) # Write the modified lines + file.truncate() # Truncate the file to the new size diff --git a/docs/docs-readme.md b/docs/docs-readme.md new file mode 100644 index 0000000..77d187b --- /dev/null +++ b/docs/docs-readme.md @@ -0,0 +1,9 @@ +To build the docs you will need to install `pandoc` to convert the `readme.md` into `rst` (the format used by `sphinx`, the docs builder). You can see the installation instructions [here](https://pandoc.org/installing.html). + +Then you can build the docs with: + +```bash +rm -r _build +pandoc -s ../readme.md -o readme.rst +pipenv run sphinx-build -M html . _build +``` diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..3f77d08 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,29 @@ +.. pybela documentation master file, created by + sphinx-quickstart on Tue Aug 6 18:36:50 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +pybela docs +=========== +Welcome to pybela’s documentation! + +.. include:: readme.rst + +.. toctree:: + :maxdepth: 2 + :caption: Getting started with pybela + :hidden: + + readme + +.. toctree:: + :caption: Module documentation + :maxdepth: 4 + :hidden: + + genindex + modules + + + + diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 0000000..320ac01 --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,25 @@ + +.. automodule:: pybela.Watcher + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pybela.Streamer + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pybela.Logger + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pybela.Monitor + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: pybela.Controller + :members: + :undoc-members: + :show-inheritance: diff --git a/pybela/Controller.py b/pybela/Controller.py index 345b9d2..582f389 100644 --- a/pybela/Controller.py +++ b/pybela/Controller.py @@ -1,6 +1,6 @@ import asyncio from .Watcher import Watcher -from .utils import print_info, print_warning +from .utils import _print_info, _print_warning class Controller(Watcher): @@ -40,7 +40,7 @@ async def async_wait_for_control_mode_to_be_set(variables=variables): asyncio.run(async_wait_for_control_mode_to_be_set(variables=variables)) - print_info( + _print_info( f"Started controlling variables {variables}... Run stop_controlling() to stop controlling the variable values.") def stop_controlling(self, variables=[]): @@ -65,7 +65,7 @@ async def async_wait_for_control_mode_to_be_set(variables=variables): asyncio.run(async_wait_for_control_mode_to_be_set(variables=variables)) - print_info(f"Stopped controlling variables {variables}.") + _print_info(f"Stopped controlling variables {variables}.") def send_value(self, variables, values): """Send a value to the given variables. @@ -90,7 +90,7 @@ def send_value(self, variables, values): value = values[variables.index(var)] if value % 1 != 0 and _type in ["i", "j"]: - print_warning( + _print_warning( f"Value {value} is not an integer, but the variable {var} is of type {_type}. Only the integer part will be sent.") self.send_ctrl_msg( diff --git a/pybela/Logger.py b/pybela/Logger.py index b01ee4e..613f195 100644 --- a/pybela/Logger.py +++ b/pybela/Logger.py @@ -4,7 +4,7 @@ import struct import paramiko from .Watcher import Watcher -from .utils import bcolors, print_error, print_info, print_ok, print_warning +from .utils import _bcolors, _print_error, _print_info, _print_ok, _print_warning class Logger(Watcher): @@ -46,17 +46,21 @@ def start_logging(self, variables=[], transfer=True, logging_dir="./"): local_paths = {} if transfer: - for var in [v for v in self.watcher_vars if v["name"] in variables]: - var = var["name"] - local_path = os.path.join( - logging_dir, os.path.basename(remote_paths[var])) + async def copying_tasks(): # FIXME can we remove this async? + for var in [v for v in self.watcher_vars if v["name"] in variables]: + var = var["name"] + local_path = os.path.join( + logging_dir, os.path.basename(remote_paths[var])) - # if file already exists, throw a warning and add number at the end of the filename - local_paths[var] = self._generate_local_filename(local_path) + # if file already exists, throw a warning and add number at the end of the filename + local_paths[var] = self._generate_local_filename( + local_path) - copying_task = self.copy_file_in_chunks( - remote_paths[var], local_paths[var]) - self._active_copying_tasks.append(copying_task) + copying_task = self.__copy_file_in_chunks( + remote_paths[var], local_paths[var]) + self._active_copying_tasks.append(copying_task) + + asyncio.run(copying_tasks()) return {"local_paths": local_paths, "remote_paths": remote_paths} @@ -96,7 +100,7 @@ async def _async_check_if_file_exists_and_start_copying(var, timestamp): if remote_file_size > 0: # white till first buffers are written into the file _has_file_been_created = 1 - print_info( + _print_info( f"Logging started for {var}...") break # Break the loop if the remote file size is non-zero @@ -114,7 +118,7 @@ async def _async_check_if_file_exists_and_start_copying(var, timestamp): local_paths[var] = self._generate_local_filename( local_path) - copying_task = self.copy_file_in_chunks( + copying_task = self.__copy_file_in_chunks( remote_paths[var], local_paths[var]) self._active_copying_tasks.append(copying_task) @@ -157,27 +161,23 @@ def __logging_common_routine(self, mode, timestamps=[], durations=[], variables= if self.is_logging(): self.stop_logging() - # self.start() # start websocket connection -- done with .connect() self.connect_ssh() # start ssh connection self._logging_mode = mode - async def _async_send_logging_cmd_and_wait_for_response(var): - # the logger responds with the name of the file where the variable is being logged in Bela - # the logger responds with one message per variable -- so to keep track of responses it is easier to ask for a variable at a time rather than all at once - self.send_ctrl_msg( - {"watcher": [{"cmd": "log", "timestamps": timestamps, "durations": durations, "watchers": [var]}]}) - await self._log_response_available.wait() - self._log_response_available.clear() - return self._log_response - - remote_files = {} - remote_paths = {} - for var in variables: - remote_files[var] = asyncio.run(_async_send_logging_cmd_and_wait_for_response(var))[ - "logFileName"] + remote_files, remote_paths = {}, {} + + self.send_ctrl_msg({"watcher": [ + {"cmd": "log", "timestamps": timestamps, "durations": durations, "watchers": variables}]}) + list_res = self.list() + + for idx, var in enumerate(variables): + remote_files[var] = list_res["watchers"][idx]["logFileName"] remote_paths[var] = f'/root/Bela/projects/{self.project_name}/{remote_files[var]}' + _print_info( + f"Started logging variables {variables}... Run stop_logging() to stop logging.") + return remote_paths def stop_logging(self, variables=[]): @@ -195,6 +195,8 @@ async def async_stop_logging(variables=[]): self.send_ctrl_msg( {"watcher": [{"cmd": "unlog", "watchers": variables}]}) + _print_info(f"Stopped logging variables {variables}...") + await asyncio.gather(*self._active_copying_tasks, return_exceptions=True) self._active_copying_tasks.clear() @@ -222,7 +224,7 @@ def connect_ssh(self): except paramiko.SSHException as e: self.ssh_client.get_transport().auth_none("root") except Exception as e: - print_error( + _print_error( f"Error while connecting to Bela via ssh: {e} {bcolors.ENDC}") return @@ -244,65 +246,6 @@ def is_logging(self): """ return True if self._logging_mode != "OFF" else False - def copy_file_in_chunks(self, remote_path, local_path, chunk_size=2**12): - """ Copies a file from the remote path to the local path in chunks. This function is called by start_logging() if transfer=True. - - Args: - remote_path (str): Path to the file in Bela. - local_path (str): Path to the file in the local machine (where the file is copied to) - chunk_size (int, optional): Chunk size. Defaults to 2**12. - - Returns: - asyncio.Task: Task that copies the file in chunks. - """ - - async def async_copy_file_in_chunks(remote_path, local_path, chunk_size=2**12): - - while True: - # Wait for a second before checking again - await asyncio.sleep(1) - - try: - remote_file = self.sftp_client.open(remote_path, 'rb') - remote_file_size = self.sftp_client.stat( - remote_path).st_size - - if remote_file_size > 0: # white till first buffers are written into the file - break # Break the loop if the remote file size is non-zero - - except FileNotFoundError: - print_error( - f"Remote file '{remote_path}' does not exist.") - return None - - try: - async with aiofiles.open(local_path, 'wb') as local_file: - while True: - chunk = remote_file.read(chunk_size) - # keep checking file whilst logging is still going on (in case a variable fills the buffers slowly) - if not chunk and self._logging_mode == "OFF": - await asyncio.sleep(0.1) # flushed data - break - await local_file.write(chunk) - print_ok( - f"\rTransferring {remote_path}-->{local_path}...", end="", flush=True) - await asyncio.sleep(0.1) - chunk = remote_file.read() - if chunk: - await local_file.write(chunk) - remote_file.close() - print_ok("Done.") - - except Exception as e: - print_error( - f"Error while transferring file: {e}.") - return None - - finally: - await self._async_remove_item_from_list(self._active_copying_tasks, asyncio.current_task()) - - return asyncio.create_task(async_copy_file_in_chunks(remote_path, local_path, chunk_size)) - def read_binary_file(self, file_path, timestamp_mode): """ Reads a binary file generated by the logger and returns a dictionary with the file contents. @@ -357,7 +300,7 @@ def _parse_null_terminated_string(file): parsed_buffers.append(_parsed_buffer) except struct.error as e: - print_error(str(e)) + _print_error(str(e)) break # No more data to read return { @@ -373,6 +316,65 @@ def _parse_null_terminated_string(file): # -- ssh copy utils + def __copy_file_in_chunks(self, remote_path, local_path, chunk_size=2**12): + """ Copies a file from the remote path to the local path in chunks. This function is called by start_logging() if transfer=True. + + Args: + remote_path (str): Path to the file in Bela. + local_path (str): Path to the file in the local machine (where the file is copied to) + chunk_size (int, optional): Chunk size. Defaults to 2**12. + + Returns: + asyncio.Task: Task that copies the file in chunks. + """ + + async def async_copy_file_in_chunks(remote_path, local_path, chunk_size=2**12): + + while True: + # Wait for a second before checking again + await asyncio.sleep(1) # TODO can this be lower? + + try: + remote_file = self.sftp_client.open(remote_path, 'rb') + remote_file_size = self.sftp_client.stat( + remote_path).st_size + + if remote_file_size > 0: # white till first buffers are written into the file + break # Break the loop if the remote file size is non-zero + + except FileNotFoundError: + _print_error( + f"Remote file '{remote_path}' does not exist.") + return None + + try: + async with aiofiles.open(local_path, 'wb') as local_file: + while True: + chunk = remote_file.read(chunk_size) + # keep checking file whilst logging is still going on (in case a variable fills the buffers slowly) + if not chunk and self._logging_mode == "OFF": + await asyncio.sleep(0.1) # flushed data + break + await local_file.write(chunk) + _print_ok( + f"\rTransferring {remote_path}-->{local_path}...", end="", flush=True) + await asyncio.sleep(0.1) + chunk = remote_file.read() + if chunk: + await local_file.write(chunk) + remote_file.close() + _print_ok("Done.") + + except Exception as e: + _print_error( + f"Error while transferring file: {e}.") + return None + + finally: + await self._async_remove_item_from_list(self._active_copying_tasks, asyncio.current_task()) + + return asyncio.create_task(async_copy_file_in_chunks(remote_path, local_path, chunk_size)) + def copy_file_from_bela(self, remote_path, local_path, verbose=True): """Copy a file from Bela onto the local machine. @@ -404,10 +406,10 @@ def copy_all_bin_files_in_project(self, dir="./", verbose=True): *copy_tasks, return_exceptions=True)) if verbose: - print_ok( + _print_ok( f"All .bin files in {remote_path} have been copied to {dir}.") except Exception as e: - print_error( + _print_error( f"Error copying .bin files in {remote_path}: {e}") finally: self.disconnect_ssh() @@ -428,16 +430,63 @@ def callback(transferred, to_transfer): return transferred_event.set( self.sftp_client.get(remote_path, local_path, callback=callback) await asyncio.wait_for(transferred_event.wait(), timeout=3) if verbose: - print_ok( + _print_ok( f"\rTransferring {remote_path}-->{local_path}... Done.") return transferred_event.is_set() except asyncio.TimeoutError: - print_error("Timeout while transferring file.") + _print_error("Timeout while transferring file.") return False # File copy did not complete within the timeout except Exception as e: - print_error(f"Error while transferring file: {e}") + _print_error(f"Error while transferring file: {e}") return False + def finish_copying_file(self, remote_path, local_path): # TODO test + """Finish copying file if it was interrupted. This function is used to copy the remaining part of a file that was interrupted during the copy process. + + Args: + remote_path (str): Path to the file in Bela. + local_path (str): Path to the file in the local machine (where the file is copied to) + """ + self.connect_ssh() + + try: + remote_file = self.sftp_client.open(remote_path, 'rb') + remote_file_size = self.sftp_client.stat( + remote_path).st_size + except FileNotFoundError: + _print_error( + f"Remote file '{remote_path}' does not exist.") + self.disconnect_ssh() + return None + if not os.path.exists(local_path): + _print_error( + f"Local file '{local_path}' does not exist. If you want to copy a file that hasn't been partially copied yet, use copy_file_from_bela() instead.") + self.disconnect_ssh() + return None + local_file_size = os.path.getsize(local_path) + + try: + if local_file_size < remote_file_size: + # Calculate the remaining part to copy + remaining_size = remote_file_size - local_file_size + # Use readv to read the remaining part of the file + chunks = [(local_file_size, remaining_size)] + data = remote_file.readv(chunks) + + _print_ok( + f"\rTransferring {remote_path}-->{local_path}...", end="", flush=True) + # Append the data to the local file + with open(local_path, 'ab') as local_file: + local_file.write(data) + _print_ok("Done.") + else: + _print_error( + "Local file is already up-to-date or larger than the remote file.") + except Exception as e: + _print_error(f"Error finishing file copy: {e}") + + self.disconnect_ssh() + # -- ssh delete utils def delete_file_from_bela(self, remote_path, verbose=True): @@ -464,10 +513,10 @@ def delete_all_bin_files_in_project(self, verbose=True): *deletion_tasks, return_exceptions=True)) if verbose: - print_ok( + _print_ok( f"All .bin files in {remote_path} have been removed.") except Exception as e: - print_error( + _print_error( f"Error deleting .bin files in {remote_path}: {e}") finally: self.disconnect_ssh() @@ -477,7 +526,7 @@ async def _async_delete_file_from_bela(self, remote_path, verbose=True): try: self.sftp_client.stat(remote_path) # check if file exists except FileNotFoundError: - print_error( + _print_error( f"Error: Remote file '{remote_path}' does not exist.") return @@ -489,11 +538,11 @@ async def _async_delete_file_from_bela(self, remote_path, verbose=True): except FileNotFoundError: # File does not exist, it has been successfully removed if verbose: - print_ok( + _print_ok( f"File '{remote_path}' has been removed from Bela.") break except Exception as e: - print_error( + _print_error( f"Error while deleting file in Bela: {e} ") break @@ -504,24 +553,28 @@ def _action_on_all_bin_files_in_project(self, action, local_dir=None): remote_path = f'/root/Bela/projects/{self.project_name}' file_list = self.sftp_client.listdir(remote_path) if len(file_list) == 0: - print_warning(f"No .bin files in {remote_path}.") + _print_warning(f"No .bin files in {remote_path}.") return # Iterate through the files and delete .bin files tasks = [] - for file_name in file_list: - if file_name.endswith('.bin'): - remote_file_path = f"{remote_path}/{file_name}" - if action == "delete": - task = asyncio.create_task( - self._async_delete_file_from_bela(remote_file_path)) - elif action == "copy": - local_filename = os.path.join(local_dir, file_name) - task = asyncio.create_task( - self._async_copy_file_from_bela(remote_file_path, local_filename)) - else: - raise ValueError(f"Invalid action: {action}") - tasks.append(task) + + async def _async_action_action_on_all_bin_files_in_project(): # FIXME can we avoid this async? + for file_name in file_list: + if file_name.endswith('.bin'): + remote_file_path = f"{remote_path}/{file_name}" + if action == "delete": + task = asyncio.create_task( + self._async_delete_file_from_bela(remote_file_path)) + elif action == "copy": + local_filename = os.path.join(local_dir, file_name) + task = asyncio.create_task( + self._async_copy_file_from_bela(remote_file_path, local_filename)) + else: + raise ValueError(f"Invalid action: {action}") + tasks.append(task) + + asyncio.run(_async_action_action_on_all_bin_files_in_project()) return tasks diff --git a/pybela/Streamer.py b/pybela/Streamer.py index bd21fe6..69ec068 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -1,3 +1,4 @@ +import asyncio.base_subprocess import json import copy import os @@ -8,13 +9,14 @@ from itertools import cycle import warnings import re +import struct import bokeh.plotting import bokeh.io import bokeh.driving from bokeh.resources import INLINE from .Watcher import Watcher -from .utils import print_info, print_error, print_warning +from .utils import _print_info, _print_error, _print_warning class Streamer(Watcher): @@ -30,23 +32,31 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add super(Streamer, self).__init__(ip, port, data_add, control_add) - # stores the list of monitored variables for each monitored session. cleaned after each monitoring session. used to avoid calling list() every time a new message is parsed - self._monitored_vars = None - + # -- streaming -- + self._streaming_mode = "OFF" # OFF, FOREVER, N_VALUES, PEEK :: this flag prevents writing into the streaming buffer unless requested by the user using the start/stop_streaming() functions + self._streaming_buffer_available = asyncio.Event() # number of streaming buffers (not of data points!) self._streaming_buffers_queue_length = 1000 self._streaming_buffers_queue = None self.last_streamed_buffer = {} - self._streaming_mode = "OFF" # OFF, FOREVER, N_VALUES, PEEK :: this flag prevents writing into the streaming buffer unless requested by the user using the start/stop_streaming() functions - self._streaming_buffer_available = asyncio.Event() + # -- on data/block callbacks -- + self._processed_data_msg_queue = asyncio.Queue() + self._on_buffer_callback_is_active = False + self._on_buffer_callback_worker_task = None + self._on_block_callback_is_active = False + self._on_block_callback_worker_task = None + # -- save -- self._saving_enabled = False self._saving_filename = None self._saving_task = None self._active_saving_tasks = [] self._saving_file_locks = {} + # -- monitor -- + # stores the list of monitored variables for each monitored session. cleaned after each monitoring session. used to avoid calling list() every time a new message is parsed + self._monitored_vars = None self._peek_response_available = asyncio.Event() self._peek_response = None @@ -98,19 +108,6 @@ def streaming_buffers_queue(self): # returns a dict of lists instead of a dict of dequeues return {key: list(value) for key, value in self._streaming_buffers_queue.items()} - def start(self): - """Starts the websocket connection and initialises the streaming buffers queue. - """ - # self.connect() - if not self.is_connected(): - print_warning( - f'{"Monitor" if self._mode=="MONITOR" else "Streamer" } is not connected to Bela. Run {"monitor" if self._mode=="MONITOR" else "streamer"}.connect() first.') - return 0 - self._streaming_buffers_queue = {var["name"]: deque( - maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} - self.last_streamed_buffer = { - var["name"]: {"data": [], "timestamps": []} for var in self.watcher_vars} - @property def streaming_buffers_data(self): """Returns a dict where each key corresponds to a variable and each value to a flat list of the streamed values. Does not return timestamps of each datapoint since that depends on how often the variables are reassigned in the Bela code. @@ -127,14 +124,20 @@ def streaming_buffers_data(self): # - streaming methods - def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./"): + def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./", on_buffer_callback=None, on_block_callback=None, callback_args=()): if self.is_streaming(): - print_warning("Stopping previous streaming session...") + _print_warning("Stopping previous streaming session...") self.stop_streaming() # stop any previous streaming - if self.start() == 0: # bela is not connected - return + if not self.is_connected(): + _print_warning( + f'{"Monitor" if self._mode=="MONITOR" else "Streamer" } is not connected to Bela. Run {"monitor" if self._mode=="MONITOR" else "streamer"}.connect() first.') + return 0 + self._streaming_buffers_queue = {var["name"]: deque( + maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} + self.last_streamed_buffer = { + var["name"]: {"data": [], "timestamps": []} for var in self.watcher_vars} if not os.path.exists(saving_dir): os.makedirs(saving_dir) @@ -143,10 +146,30 @@ def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_ self._saving_filename = self._generate_filename( saving_filename, saving_dir) if saving_enabled else None + self._processed_data_msg_queue = asyncio.Queue() # clear processed data queue + + async def async_callback_workers(): + + if on_block_callback and on_buffer_callback: + _print_error( + "Error: Both on_buffer_callback and on_block_callback cannot be enabled at the same time.") + return 0 + if on_buffer_callback: + self._on_buffer_callback_is_active = True + self._on_buffer_callback_worker_task = asyncio.create_task( + self.__async_on_buffer_callback_worker(on_buffer_callback, callback_args)) + + elif on_block_callback: + self._on_block_callback_is_active = True + self._on_block_callback_worker_task = asyncio.create_task( + self.__async_on_block_callback_worker(on_block_callback, callback_args, variables)) + + asyncio.run(async_callback_workers()) + # checks types and if no variables are specified, stream all watcher variables (default) - variables = self._var_arg_checker(variables) + return self._var_arg_checker(variables) - def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./"): + def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./", on_buffer_callback=None, on_block_callback=None, callback_args=()): """ Starts the streaming session. The session can be stopped with stop_streaming(). @@ -157,10 +180,24 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving periods (list, optional): List of streaming periods. Streaming periods are used by the monitor and will be ignored if in streaming mode. Defaults to []. saving_enabled (bool, optional): Enables/disables saving streamed data to local file. Defaults to False. saving_filename (str, optional) Filename for saving the streamed data. Defaults to None. + on_buffer_callback (function, optional). Callback function that is called every time a buffer is received. The callback function should take a single argument, the buffer. Accepts asynchronous functions (defined with async def). Defaults to None. + on_block_callback (function, optional). Callback function that is called every time a block of buffers is received. A block of buffers is a list of buffers, one for each streamed variable. The callback function should take a single argument, a list of buffers. Accepts asynchronous functions (defined with async def). Defaults to None. + callback_args (tuple, optional): Arguments to pass to the callback functions. Defaults to (). + """ - self.__streaming_common_routine( - variables, saving_enabled, saving_filename, saving_dir) + variables = self.__streaming_common_routine( + variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback, callback_args) + + # commented because then you can only start streaming on variables whose values have been previously assigned in the Bela code + # not useful for the Sender function (send a buffer from the laptop and stream it through the watcher) + # async def async_wait_for_streaming_to_start(): # ensures that when function returns streaming has started + # if self._mode == "STREAM": + # while set([var["name"] for var in self.watched_vars]) != set(variables): + # await asyncio.sleep(0.1) + # elif self._mode == "MONITOR": + # while not all(self._streaming_buffers_queue_insertion_counts[var] > 0 for var in variables): + # await asyncio.sleep(0.1) self._streaming_mode = "FOREVER" if self._peek_response is None else "PEEK" if self._mode == "STREAM": @@ -169,17 +206,19 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving "Periods list is ignored in streaming mode STREAM") self.send_ctrl_msg( {"watcher": [{"cmd": "watch", "watchers": variables}]}) - print_info( + # asyncio.run(async_wait_for_streaming_to_start()) + _print_info( f"Started streaming variables {variables}... Run stop_streaming() to stop streaming.") elif self._mode == "MONITOR": periods = self._check_periods(periods, variables) self.send_ctrl_msg( {"watcher": [{"cmd": "monitor", "watchers": variables, "periods": periods}]}) + # asyncio.run(async_wait_for_streaming_to_start()) if self._streaming_mode == "FOREVER": - print_info( + _print_info( f"Started monitoring variables {variables}... Run stop_monitoring() to stop monitoring.") elif self._streaming_mode == "PEEK": - print_info(f"Peeking at variables {variables}...") + _print_info(f"Peeking at variables {variables}...") def stop_streaming(self, variables=[]): """ @@ -197,6 +236,7 @@ async def async_stop_streaming(variables=[]): _previous_streaming_mode = copy.copy(self._streaming_mode) self._streaming_mode = "OFF" + if self._saving_enabled: self._saving_enabled = False self._saving_filename = None @@ -211,16 +251,23 @@ async def async_stop_streaming(variables=[]): if self._mode == "STREAM" and _previous_streaming_mode != "SCHEDULE": self.send_ctrl_msg( {"watcher": [{"cmd": "unwatch", "watchers": variables}]}) - print_info(f"Stopped streaming variables {variables}...") + _print_info(f"Stopped streaming variables {variables}...") elif self._mode == "MONITOR" and _previous_streaming_mode != "SCHEDULE": self.send_ctrl_msg( {"watcher": [{"cmd": "monitor", "periods": [0]*len(variables), "watchers": variables}]}) # setting period to 0 disables monitoring if not _previous_streaming_mode == "PEEK": - print_info(f"Stopped monitoring variables {variables}...") + _print_info(f"Stopped monitoring variables {variables}...") + self._processed_data_msg_queue = asyncio.Queue() # clear processed data queue + self._on_buffer_callback_is_active = False + if self._on_buffer_callback_worker_task: + self._on_buffer_callback_worker_task.cancel() + self._on_block_callback_is_active = False + if self._on_block_callback_worker_task: + self._on_block_callback_worker_task.cancel() return asyncio.run(async_stop_streaming(variables)) - def schedule_streaming(self, variables=[], timestamps=[], durations=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./"): + def schedule_streaming(self, variables=[], timestamps=[], durations=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./", on_buffer_callback=None, on_block_callback=None, callback_args=()): """Schedule streaming of variables. The streaming session can be stopped with stop_streaming(). Args: @@ -231,10 +278,13 @@ def schedule_streaming(self, variables=[], timestamps=[], durations=[], saving_e saving_enabled (bool, optional): Enables/disables saving streamed data to local file. Defaults to False. saving_filename (str, optional) Filename for saving the streamed data. Defaults to None. saving_dir (str, optional): Directory for saving the streamed data files. Defaults to "./". + on_buffer_callback (function, optional). Callback function that is called every time a buffer is received. The callback function should take a single argument, the buffer. Accepts asynchronous functions (defined with async def). Defaults to None. + on_block_callback (function, optional). Callback function that is called every time a block of buffers is received. A block of buffers is a list of buffers, one for each streamed variable. The callback function should take a single argument, a list of buffers. Accepts asynchronous functions (defined with async def). Defaults to None. + callback_args (tuple, optional): Arguments to pass to the callback functions. Defaults to (). """ - self.__streaming_common_routine( - variables, saving_enabled, saving_filename, saving_dir) + variables = self.__streaming_common_routine( + variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback, callback_args) self._streaming_mode = "SCHEDULE" @@ -251,12 +301,12 @@ async def async_check_if_variables_have_been_streamed_and_stop(): for var in [v["name"] for v in self.watched_vars]: if var not in started_streaming_vars: started_streaming_vars.append(var) - print_info(f"Started streaming {var}...") + _print_info(f"Started streaming {var}...") for var in started_streaming_vars: if var not in [v["name"] for v in self.watched_vars]: finished_streaming_vars.append(var) - print_info(f"Stopped streaming {var}") + _print_info(f"Stopped streaming {var}") await asyncio.sleep(0.1) @@ -265,7 +315,7 @@ async def async_check_if_variables_have_been_streamed_and_stop(): asyncio.run( async_check_if_variables_have_been_streamed_and_stop()) - def stream_n_values(self, variables=[], periods=[], n_values=1000, saving_enabled=False, saving_filename=None, saving_dir="./"): + def stream_n_values(self, variables=[], periods=[], n_values=1000, saving_enabled=False, saving_filename=None, saving_dir="./", on_buffer_callback=None, on_block_callback=None, callback_args=()): """ Streams a given number of values. Since the data comes in buffers of a predefined size, always an extra number of frames will be streamed (unless the number of frames is a multiple of the buffer size). @@ -284,13 +334,16 @@ def stream_n_values(self, variables=[], periods=[], n_values=1000, saving_enable saving_enabled (bool, optional): Enables/disables saving streamed data to local file. Defaults to False. saving_filename (str, optional) Filename for saving the streamed data. Defaults to None. saving_dir (str, optional): Directory for saving the streamed data. Defaults to "./". + on_buffer_callback (function, optional). Callback function that is called every time a buffer is received. The callback function should take a single argument, the buffer. Accepts asynchronous functions (defined with async def). Defaults to None. + on_block_callback (function, optional). Callback function that is called every time a block of buffers is received. A block of buffers is a list of buffers, one for each streamed variable. The callback function should take a single argument, a list of buffers. Accepts asynchronous functions (defined with async def). Defaults to None. + callback_args (tuple, optional): Arguments to pass to the callback functions. Defaults to (). Returns: streaming_buffers_queue (dict): Dict containing the streaming buffers for each streamed variable. """ - return asyncio.run(self.async_stream_n_values(variables, periods, n_values, saving_enabled, saving_filename, saving_dir)) + return asyncio.run(self.async_stream_n_values(variables, periods, n_values, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback, callback_args)) - async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./"): + async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./", on_buffer_callback=None, on_block_callback=None, callback_args=()): """ Asynchronous version of stream_n_values(). Usage: stream_task = asyncio.create_task(streamer.async_stream_n_values(variables, n_values, saving_enabled, saving_filename)) @@ -305,14 +358,17 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s saving_enabled (bool, optional): Enables/disables saving streamed data to local file. Defaults to False. saving_filename (str, optional) Filename for saving the streamed data. Defaults to None. saving_dir (str, optional): Directory for saving the streamed data. Defaults to "./". + on_buffer_callback (function, optional). Callback function that is called every time a buffer is received. The callback function should take a single argument, the buffer. Accepts asynchronous functions (defined with async def). Defaults to None. + on_block_callback (function, optional). Callback function that is called every time a block of buffers is received. A block of buffers is a list of buffers, one for each streamed variable. The callback function should take a single argument, a list of buffers. Accepts asynchronous functions (defined with async def). Defaults to None. + callback_args (tuple, optional): Arguments to pass to the callback functions. Defaults to (). Returns: deque: Streaming buffers queue """ # resizes the streaming buffer size to n_values and returns it when full - self.__streaming_common_routine( - variables=variables, saving_enabled=saving_enabled, saving_filename=saving_filename, saving_dir=saving_dir) + variables = self.__streaming_common_routine( + variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback, callback_args) self._streaming_mode = "N_VALUES" # flag cleared in __rec_msg_callback @@ -336,7 +392,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s "Periods list is ignored in streaming mode STREAM") self.send_ctrl_msg( {"watcher": [{"cmd": "unwatch", "watchers": [var["name"] for var in self.watcher_vars]}, {"cmd": "watch", "watchers": variables}]}) - print_info( + _print_info( f"Streaming {n_values} values for variables {variables}...") elif self._mode == "MONITOR": @@ -346,7 +402,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s periods = self._check_periods(periods, variables) self.send_ctrl_msg( {"watcher": [{"cmd": "monitor", "watchers": variables, "periods": periods}]}) - print_info( + _print_info( f"Monitoring {n_values} values for variables {variables} with periods {periods}...") # await until streaming buffer is full @@ -360,6 +416,89 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s return self.streaming_buffers_queue + # callbacks + + async def __async_on_buffer_callback_worker(self, on_buffer_callback, callback_args): + while self._on_buffer_callback_is_active and self.is_streaming(): + if not self._processed_data_msg_queue.empty(): + msg = await self._processed_data_msg_queue.get() + self._processed_data_msg_queue.task_done() + try: + if asyncio.iscoroutinefunction(on_buffer_callback): + if callback_args != () and type(callback_args) == tuple: + await on_buffer_callback(msg, *callback_args) + elif callback_args != (): + await on_buffer_callback(msg, callback_args) + else: + await on_buffer_callback(msg) + else: + if callback_args != () and type(callback_args) == tuple: + on_buffer_callback(msg, *callback_args) + elif callback_args != (): + on_buffer_callback(msg, callback_args) + else: + on_buffer_callback(msg) + except Exception as e: + _print_error( + f"Error in on_buffer_callback: {e}") + + await asyncio.sleep(0.0001) + + async def __async_on_block_callback_worker(self, on_block_callback, callback_args, variables): + while self._on_block_callback_is_active and self.is_streaming(): + msgs = [] + for var in variables: + # if not self._processed_data_msg_queue.empty(): + msg = await asyncio.wait_for(self._processed_data_msg_queue.get(), timeout=1) + msgs.append(msg) + self._processed_data_msg_queue.task_done() + if len(msgs) == len(variables): + try: + if asyncio.iscoroutinefunction(on_block_callback): + if callback_args != () and type(callback_args) == tuple: + await on_block_callback(msgs, *callback_args) + elif callback_args != (): + await on_block_callback(msgs, callback_args) + else: + await on_block_callback(msgs) + else: + if callback_args != () and type(callback_args) == tuple: + on_block_callback(msgs, *callback_args) + elif callback_args != (): + on_block_callback(msgs, callback_args) + else: + on_block_callback(msgs) + + except Exception as e: + _print_error( + f"Error in on_block_callback: {e}") + + await asyncio.sleep(0.001) + + # send + + def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list, verbose=False): + """ + Sends a buffer to Bela. The buffer is packed into binary format and sent over the websocket. + + Args: + buffer_id (int): Buffer id + buffer_type (str): Buffer type. Supported types are 'i' (int), 'f' (float), 'j' (uint), 'd' (double), 'c' (char). + buffer_length (int): Buffer length + data_list (list): List of data to be sent + """ + # Pack the data into binary format + # >I means big-endian unsigned int, 4s means 4-byte string, pad with x for empty bytes + + idtypestr = struct.pack(' 1 and not all([len(self.last_streamed_buffer[y_var]) == len(self.last_streamed_buffer[y_vars[0]]) for y_var in y_vars[1:]]): - print_error( + _print_error( "PlottingError: plotting buffers of different length is not supported yet. Try using the same timestamp mode and type for your variables...") return @@ -500,12 +645,13 @@ async def wait_for_streaming_buffers_to_arrive(): # --- private methods --- # - def _process_data_msg(self, msg): + async def _process_data_msg(self, msg): """ Process data message received from Bela. This function is called by the websocket listener when a data message is received. Args: msg (bytestring): Data message received from Bela """ + global _channel, _type try: _, __ = _channel, _type @@ -539,8 +685,12 @@ def _process_data_msg(self, msg): parsed_buffer = self._parse_binary_data( msg, var_timestamp_mode, _type).copy() + # put in processed_queue if callback is true + if self._on_buffer_callback_is_active or self._on_block_callback_is_active: + await self._processed_data_msg_queue.put({"name": var_name, "buffer": parsed_buffer}) + # fixes bug where data is shifted by period - _var_streaming_buffers_queue = copy.deepcopy( + _var_streaming_buffers_queue = copy.copy( self._streaming_buffers_queue[var_name]) _var_streaming_buffers_queue.append(parsed_buffer) self._streaming_buffers_queue[var_name] = _var_streaming_buffers_queue @@ -604,7 +754,7 @@ async def _save_data_to_file(self, filename, msg): await f.write(_json+"\n") except Exception as e: - print_error(f"Error while saving data to file: {e}") + _print_error(f"Error while saving data to file: {e}") finally: await self._async_remove_item_from_list(self._active_saving_tasks, asyncio.current_task()) @@ -655,7 +805,7 @@ def _check_periods(self, periods, variables): if isinstance(periods, int): periods = [periods]*len(variables) elif periods == []: - print_warning("No periods passed, using default value of 1000") + _print_warning("No periods passed, using default value of 1000") periods = [1000]*len(variables) for period in periods: diff --git a/pybela/Watcher.py b/pybela/Watcher.py index 4228b90..88f9599 100644 --- a/pybela/Watcher.py +++ b/pybela/Watcher.py @@ -5,7 +5,7 @@ import errno import struct import os -from .utils import print_error, print_warning, print_ok +from .utils import _print_error, _print_warning, _print_ok class Watcher: @@ -32,10 +32,15 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add self.ws_ctrl = None self.ws_data = None - self._list_response_available = asyncio.Event() - self._list_response = None - self._log_response_available = asyncio.Event() - self._log_response = None + self._list_response_queue = asyncio.Queue() + + # receive data message queue + self._received_data_msg_queue = asyncio.Queue() + self._process_received_data_msg_worker_task = None + + # send data message queue + self._to_send_data_msg_queue = asyncio.Queue() + self._sending_data_msg_worker_task = None self._watcher_vars = None @@ -109,15 +114,15 @@ async def _async_connect(): try: # Close any open ctrl websocket open for the same mode (STREAM, LOG, MONITOR, WATCH) if self._pybela_ws_register[self._mode].get(self.ws_ctrl_add) is not None and self._pybela_ws_register[self._mode][self.ws_ctrl_add].open: - print_warning( + _print_warning( f"pybela doesn't support more than one active connection at a time for a given mode. Closing previous connection for {self._mode} at {self.ws_ctrl_add}.") await self._pybela_ws_register[self._mode][self.ws_ctrl_add].close() # Control and monitor can't be used at the same time if (self._mode == "MONITOR" and self._pybela_ws_register["CONTROL"].get(self.ws_ctrl_add) is not None and self._pybela_ws_register["CONTROL"][self.ws_ctrl_add].open) or (self._mode == "CONTROL" and self._pybela_ws_register["MONITOR"].get(self.ws_ctrl_add) is not None and self._pybela_ws_register["MONITOR"][self.ws_ctrl_add].open): - print_warning( + _print_warning( f"pybela doesn't support running control and monitor modes at the same time. You are currently running {'CONTROL' if self._mode=='MONITOR' else 'MONITOR'} at {self.ws_ctrl_add}. You can close it running controller.disconnect()") - print_error("Connection failed") + _print_error("Connection failed") return 0 # Connect to the control websocket @@ -133,6 +138,10 @@ async def _async_connect(): # Connect to the data websocket self.ws_data = await websockets.connect(self.ws_data_add) + self._process_received_data_msg_worker_task = asyncio.create_task( + self._process_data_msg_worker()) + self._sending_data_msg_worker_task = asyncio.create_task( + self._send_data_msg_worker()) # Start listener loops self._start_listener(self.ws_ctrl, self.ws_ctrl_add) @@ -143,10 +152,10 @@ async def _async_connect(): self._sample_rate = self._list["sampleRate"] self._watcher_vars = self._filtered_watcher_vars(self._list["watchers"], lambda var: True) - print_ok("Connection successful") + _print_ok("Connection successful") return 1 else: - print_error("Connection failed") + _print_error("Connection failed") return 0 except Exception as e: raise ConnectionError(f"Connection failed: {str(e)}.") @@ -161,6 +170,10 @@ async def _async_stop(): await self.ws_ctrl.close() if self.ws_data is not None and self.ws_data.open: await self.ws_data.close() + if self._process_received_data_msg_worker_task is not None: + self._process_received_data_msg_worker_task.cancel() + if self._sending_data_msg_worker_task is not None: + self._sending_data_msg_worker_task.cancel() return asyncio.run(_async_stop()) def is_connected(self): @@ -172,9 +185,10 @@ def list(self): async def _async_list(): self.send_ctrl_msg({"watcher": [{"cmd": "list"}]}) # Wait for the list response to be available - await self._list_response_available.wait() - self._list_response_available.clear() # Reset the event for the next call - return self._list_response + + list_res = await self._list_response_queue.get() + self._list_response_queue.task_done() + return list_res return asyncio.run(_async_list()) @@ -184,7 +198,7 @@ def send_ctrl_msg(self, msg): Args: msg (str): Message to send to the Bela watcher. Example: {"watcher": [{"cmd": "list"}]} """ - self._send_msg(self.ws_ctrl_add, msg) + self._send_msg(self.ws_ctrl_add, json.dumps(msg)) # --- private methods --- # @@ -204,14 +218,14 @@ async def _async_start_listener(ws, ws_address): if self._printall_responses: print(msg) if ws_address == self.ws_data_add: - self._process_data_msg(msg) + self._received_data_msg_queue.put_nowait(msg) elif ws_address == self.ws_ctrl_add: self._process_ctrl_msg(msg) else: print(msg) except Exception as e: if ws.open: # otherwise websocket was closed intentionally - handle_connection_exception( + _handle_connection_exception( ws_address, e, "receiving message") asyncio.create_task( _async_start_listener(ws, ws_address)) @@ -222,24 +236,45 @@ def _send_msg(self, ws_address, msg): """Send message to websocket Args: - ws (websocket): Websocket object ws_address (str): Websocket address msg (str): Message to send """ async def _async_send_msg(ws_address, msg): try: if ws_address == self.ws_data_add and self.ws_data is not None and self.ws_data.open: - await self.ws_data.send(json.dumps(msg)) + asyncio.create_task(self._to_send_data_msg_queue.put(msg)) elif ws_address == self.ws_ctrl_add and self.ws_ctrl is not None and self.ws_ctrl.open: - await self.ws_ctrl.send(json.dumps(msg)) + await self.ws_ctrl.send(msg) except Exception as e: - handle_connection_exception(ws_address, e, "sending message") + _handle_connection_exception(ws_address, e, "sending message") return 0 asyncio.run(_async_send_msg(ws_address, msg)) + # send messages + + async def _send_data_msg_worker(self): + """ Send data message to websocket. Runs as long as websocket is open. + """ + while self.ws_data is not None and self.ws_data.open: + msg = await self._to_send_data_msg_queue.get() + await self.ws_data.send(msg) + self._to_send_data_msg_queue.task_done() + # process messages - def _process_data_msg(self, msg): # method overwritten by streamer + async def _process_data_msg_worker(self): + """Process data message. + + Args: + msg (str): Bytestring with data + """ + + while self.ws_data is not None and self.ws_data.open: + msg = await self._received_data_msg_queue.get() + await self._process_data_msg(msg) + self._received_data_msg_queue.task_done() + + async def _process_data_msg(self, msg): """Process data message. This method is overwritten by the streamer. Args: @@ -255,13 +290,9 @@ def _process_ctrl_msg(self, msg): """ _msg = json.loads(msg) - if "watcher" in _msg.keys(): - if "logFileName" in _msg["watcher"].keys(): # response to log cmd - self._log_response = _msg["watcher"] - self._log_response_available.set() - elif "sampleRate" in _msg["watcher"].keys(): # response to list cmd - self._list_response = _msg["watcher"] - self._list_response_available.set() + # response to list cmd + if "watcher" in _msg.keys() and "sampleRate" in _msg["watcher"].keys(): + self._list_response_queue.put_nowait(_msg["watcher"]) def _parse_binary_data(self, binary_data, timestamp_mode, _type): """Binary data parser. This method is used both by the streamer and the logger to parse the binary data buffers. @@ -385,7 +416,7 @@ def _generate_local_filename(self, local_path): while os.path.exists(new_local_path): new_local_path = f"{base}_{counter}{ext}" counter += 1 - print_warning( + _print_warning( f"{local_path} already exists. Renaming file to {new_local_path}") return new_local_path @@ -483,9 +514,9 @@ def __del__(self): self.stop() # stop websockets -def handle_connection_exception(ws_address, exception, action): +def _handle_connection_exception(ws_address, exception, action): bela_msg = "Make sure Bela is connected to the same network as your computer, that the IP address is correct, and that there is a project running on Bela." - print_error( + _print_error( f"WebSocket exception while connecting to {ws_address}: {exception}. {bela_msg}") if isinstance(exception, OSError): if exception.errno == errno.ECONNREFUSED: @@ -498,4 +529,4 @@ def handle_connection_exception(ws_address, exception, action): raise ConnectionError( f"Error {exception.errno} while connecting to {ws_address}. {bela_msg}") else: - print_error(f"Error while {action}: {exception}. {bela_msg}") + _print_error(f"Error while {action}: {exception}. {bela_msg}") diff --git a/pybela/utils.py b/pybela/utils.py index 305fd08..49f73dc 100644 --- a/pybela/utils.py +++ b/pybela/utils.py @@ -1,5 +1,5 @@ -class bcolors: +class _bcolors: HEADER = '\033[95m' OKBLUE = '\033[94m' OKCYAN = '\033[96m' @@ -11,17 +11,17 @@ class bcolors: UNDERLINE = '\033[4m' -def print_error(message): - print(bcolors.FAIL + message + bcolors.ENDC) +def _print_error(message): + print(_bcolors.FAIL + message + _bcolors.ENDC) -def print_info(message): - print(bcolors.OKBLUE + message + bcolors.ENDC) +def _print_info(message): + print(_bcolors.OKBLUE + message + _bcolors.ENDC) -def print_warning(message): - print(bcolors.WARNING + message + bcolors.ENDC) +def _print_warning(message): + print(_bcolors.WARNING + message + _bcolors.ENDC) -def print_ok(message, end='\n', flush=False): - print(bcolors.OKGREEN + message + bcolors.ENDC, end=end, flush=flush) +def _print_ok(message, end='\n', flush=False): + print(_bcolors.OKGREEN + message + _bcolors.ENDC, end=end, flush=flush) diff --git a/readme.md b/readme.md index d013c24..e52472e 100644 --- a/readme.md +++ b/readme.md @@ -1,47 +1,35 @@ # pybela -pybela allows interfacing with [Bela](https://bela.io/), the embedded audio platform, using Python. pybela provides a convenient way to stream, log, monitor sensor data from your Bela device to your laptop. It also allows you to control the value of variables in your Bela code from your laptop. +pybela enables seamless interfacing with [Bela](https://bela.io/), the embedded audio platform, using python. It offers a convenient way to stream data between Bela and python in both directions. In addition to data streaming, pybela supports data logging, as well as variable monitoring and control functionalities. -Below, you can find instructions to install pybela. You can find code examples at `tutorials/` and `test/`. +Below, you can find instructions to install pybela. You can find code examples at `tutorials/` and `test/`. The docs are available at [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/). -pybela was developed with a machine learning use case in mind. For a complete pipeline including data acquisition, processing, model training, and deployment (including rapid cross-compilation) check the [pybela-pytorch-xc-tutorial](https://github.com/pelinski/pybela-pytorch-xc-tutorial). +pybela was developed with a machine learning use-case in mind. For a complete pipeline including data acquisition, processing, model training, and deployment (including rapid cross-compilation) check the [pybela-pytorch-xc-tutorial](https://github.com/pelinski/pybela-pytorch-xc-tutorial). -## [Installation and set up](#installation) -You will need to (1) install the python package in your laptop, (2) set the Bela branch to `dev` and (3) add the watcher library to your Bela project. +## Installation and set up -### 1. Installing the python package +You will need to (1) install the python package in your laptop, (2) set the Bela branch to `dev` and (3) add the watcher library to your Bela project. -#### Option A: +### 1. Installing the python package -You can install this library using `pip` (replace `pip` with `pipenv` if you are using a pipenv environment): +You can install this library using `pip`: ```python pip install pybela ``` -#### Option B: - -You can also download the built package from the releases section and run (replace `pip` with `pipenv` if you are using a pipenv environment): - -```bash -pip install pybela-.tar.gz -``` +### 2. Set the Bela branch to `dev` -#### Option C: +`pybela` is relies on the `watcher` library, which currently only works with the Bela `dev` branch. To set your Bela to the `dev` branch, you can follow the instructions below. -You can also install this library using [pipenv](https://pipenv.pypa.io/en/latest/installation/) by cloning this repository and running: +**Note:** if you just flashed the Bela image, the date and time on the Bela board might be wrong, and the Bela libraries might not build correctly after changing the Bela branch. To set the correct date, you can either run (in the host) ```bash -git clone --recurse-submodules https://github.com/BelaPlatform/pybela -cd pybela -pipenv install +ssh root@bela.local "date -s \"`date '+%Y%m%d %T %z'`\"" ``` +or just open the IDE in your browser (type `bela.local` in the address bar). -### 2. Set the Bela branch to `dev` - -In order to use pybela, you will need to use the `dev` branch of the Bela code. - -#### Option A: +#### Option A: Bela connected to internet If your Bela is connected to internet, you can ssh into your Bela (`ssh root@bela.local`) and change the branch: @@ -49,10 +37,10 @@ If your Bela is connected to internet, you can ssh into your Bela (`ssh root@bel # in Bela cd Bela git checkout dev -git pull +make -f Makefile.libraries cleanall && make coreclean ``` -#### Option B: +#### Option B: Bela not connected to internet If your Bela is not connected to internet, you can change the branch by cloning the Bela repository into your laptop and then pushing the `dev` branch to your Bela. To do that, first clone the Bela repository into your laptop: @@ -78,6 +66,7 @@ Then ssh into your Bela (`ssh root@bela.local`) and change the branch: # in Bela cd Bela git checkout tmp +make -f Makefile.libraries cleanall && make coreclean ``` You can check the commit hash by running `git rev-parse --short HEAD` either on Bela or your laptop. @@ -96,16 +85,16 @@ scp watcher/Watcher.h watcher/Watcher.cpp root@bela.local:Bela/projects/your-pro pybela has three different modes of operation: -- **Streaming**: continuously send data from your Bela device to your laptop. -- **Logging**: log data in your Bela device and then retrieve it from your laptop. -- **Monitoring**: monitor the value of variables in the Bela code from your laptop. -- **Controlling**: control the value of variables in the Bela code from your laptop. +- **Streaming**: continuously send data from Bela to python (**NEW: and from python to Bela!** check the [tutorial](tutorials/notebooks/3_Streamer-python-to-Bela.ipynb)). +- **Logging**: log data in a file in Bela and then retrieve it in python. +- **Monitoring**: monitor the value of variables in the Bela code from python. +- **Controlling**: control the value of variables in the Bela code from python. -You can check the **tutorials** at `tutorials/` for more detailed information and usage of each of the modes. You can also check the `test/test.py` for a quick overview of the library. +You can check the **tutorials** at tutorials/`for more detailed information and usage of each of the modes. You can also check`test/test.py` for a quick overview of the library. ### Running the examples -The quickest way to get started is to start a jupyter notebook server and run the examples. If you haven't done it yet, install the python package as explained in the [installation section](#installation). If you don't have the `jupyter notebook` package installed, you can installed by running (replace `pip` with `pipenv` if you are using a pipenv environment): +The quickest way to get started is to start a jupyter notebook server and run the examples. If you haven't done it yet, install the python package as explained in the Installation section. If you don't have the `jupyter notebook` package installed, you can install it by running (replace `pip` with `pipenv` if you are using a pipenv environment): ```bash pip install notebook @@ -204,6 +193,10 @@ pipenv run python -m build --sdist # builds the .tar.gz file ## To do and known issues +**Long term** + +- [ ] **Design**: remove nest_asyncio? +- [ ] **Add**: example projects - [ ] **Issue:** Monitor and streamer/controller can't be used simultaneously –  This is due to both monitor and streamer both using the same websocket connection and message format. This could be fixed by having a different message format for the monitor and the streamer (e.g., adding a header to the message) - [ ] **Issue:** The plotting routine does not work when variables are updated at different rates. - [ ] **Issue**: The plotting routine does not work for the monitor (it only works for the streamer) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..31de848 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,131 @@ +-i https://pypi.org/simple +aiofiles==24.1.0 +anyio==4.4.0 +appnope==0.1.4 +argon2-cffi==23.1.0 +argon2-cffi-bindings==21.2.0 +arrow==1.3.0 +asttokens==2.4.1 +async-lru==2.0.4 +attrs==24.2.0 +babel==2.16.0 +bcrypt==4.2.0 +beautifulsoup4==4.12.3 +bitarray==2.9.2 +bleach==6.1.0 +bokeh==3.4.3 +certifi==2024.7.4 +cffi==1.17.0 +charset-normalizer==3.3.2 +comm==0.2.2 +contourpy==1.2.1 +cryptography==43.0.0 +debugpy==1.8.5 +decorator==5.1.1 +defusedxml==0.7.1 +exceptiongroup==1.2.2 +executing==2.0.1 +fastjsonschema==2.20.0 +fqdn==1.5.1 +h11==0.14.0 +httpcore==1.0.5 +httpx==0.27.0 +idna==3.7 +importlib-metadata==8.2.0 +ipykernel==6.29.5 +ipython==8.18.1 +ipywidgets==8.1.3 +isoduration==20.11.0 +jedi==0.19.1 +jinja2==3.1.4 +json5==0.9.25 +jsonpointer==3.0.0 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +jupyter==1.0.0 +jupyter-bokeh==4.0.5 +jupyter-client==8.6.2 +jupyter-console==6.6.3 +jupyter-core==5.7.2 +jupyter-events==0.10.0 +jupyter-lsp==2.2.5 +jupyter-server==2.14.2 +jupyter-server-terminals==0.5.3 +jupyterlab==4.2.4 +jupyterlab-pygments==0.3.0 +jupyterlab-server==2.27.3 +jupyterlab-widgets==3.0.11 +linkify-it-py==2.0.3 +markdown==3.6 +markdown-it-py==3.0.0 +markupsafe==2.1.5 +matplotlib-inline==0.1.7 +mdit-py-plugins==0.4.1 +mdurl==0.1.2 +mistune==3.0.2 +nbclient==0.10.0 +nbconvert==7.16.4 +nbformat==5.10.4 +nest-asyncio==1.6.0 +notebook==7.2.1 +notebook-shim==0.2.4 +numpy==1.26 +overrides==7.7.0 +packaging==24.1 +pandas==2.2.2 +pandocfilters==1.5.1 +panel==1.4.5 +param==2.1.1 +paramiko==3.4.0 +parso==0.8.4 +pexpect==4.9.0 +pillow==10.4.0 +platformdirs==4.2.2 +prometheus-client==0.20.0 +prompt-toolkit==3.0.47 +psutil==6.0.0 +ptyprocess==0.7.0 +pure-eval==0.2.3 +-e . +pycparser==2.22 +pygments==2.18.0 +pynacl==1.5.0 +python-dateutil==2.9.0.post0 +python-json-logger==2.0.7 +pytz==2024.1 +pyviz-comms==3.0.3 +pyyaml==6.0.2 +pyzmq==26.1.0 +qtconsole==5.5.2 +qtpy==2.4.1 +referencing==0.35.1 +requests==2.32.3 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rpds-py==0.20.0 +send2trash==1.8.3 +setuptools==72.1.0 +six==1.16.0 +sniffio==1.3.1 +soupsieve==2.5 +stack-data==0.6.3 +terminado==0.18.1 +tinycss2==1.3.0 +tomli==2.0.1 +tornado==6.4.1 +tqdm==4.66.5 +traitlets==5.14.3 +types-python-dateutil==2.9.0.20240316 +typing-extensions==4.12.2 +tzdata==2024.1 +uc-micro-py==1.0.3 +uri-template==1.3.0 +urllib3==2.2.2 +wcwidth==0.2.13 +webcolors==24.6.0 +webencodings==0.5.1 +websocket-client==1.8.0 +websockets==12.0 +widgetsnbextension==4.0.11 +xyzservices==2024.6.0 +zipp==3.19.2 diff --git a/setup.py b/setup.py index 37dddc0..3525073 100644 --- a/setup.py +++ b/setup.py @@ -5,10 +5,10 @@ setuptools.setup( name="pybela", - version="0.1.0", + version="1.0.0", author="Teresa Pelinski", author_email="teresapelinski@gmail.com", - description="pybela allows interfacing with Bela, the embedded audio platform, using Python. pybela provides a convenient way to stream, log, and monitor sensor data from your Bela device to your laptop, or alternatively, to send values to a Bela program from your laptop.", + description="pybela allows interfacing with Bela, the embedded audio platform, using Python. pybela provides a convenient way to stream, log, and monitor sensor data from your Bela device to your laptop, or alternatively, to stream values to a Bela program from your laptop.", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/BelaPlatform/pybela", @@ -24,7 +24,8 @@ "ipykernel", "nest-asyncio", "aiofiles", - "paramiko"], + "paramiko", + "numpy==1.26"], classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", diff --git a/test/bela-test-send/Watcher.cpp b/test/bela-test-send/Watcher.cpp new file mode 120000 index 0000000..1ef18ae --- /dev/null +++ b/test/bela-test-send/Watcher.cpp @@ -0,0 +1 @@ +../../watcher/Watcher.cpp \ No newline at end of file diff --git a/test/bela-test-send/Watcher.h b/test/bela-test-send/Watcher.h new file mode 120000 index 0000000..fc90bef --- /dev/null +++ b/test/bela-test-send/Watcher.h @@ -0,0 +1 @@ +../../watcher/Watcher.h \ No newline at end of file diff --git a/test/bela-test-send/render.cpp b/test/bela-test-send/render.cpp new file mode 100644 index 0000000..65ba8e1 --- /dev/null +++ b/test/bela-test-send/render.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +Watcher myvar1("myvar1"); +Watcher myvar2("myvar2"); + +std::vector*> myVars = {&myvar1, &myvar2}; + +struct ReceivedBuffer { + uint32_t bufferId; + char bufferType[4]; + uint32_t bufferLen; + uint32_t empty; + std::vector bufferData; +}; +ReceivedBuffer receivedBuffer; +uint receivedBufferHeaderSize; +uint64_t totalReceivedCount; + + +bool binaryDataCallback(const std::string& addr, const WSServerDetails* id, const unsigned char* data, size_t size, void* arg) { + + totalReceivedCount++; + + std::memcpy(&receivedBuffer, data, receivedBufferHeaderSize); + receivedBuffer.bufferData.resize(receivedBuffer.bufferLen); + std::memcpy(receivedBuffer.bufferData.data(), data + receivedBufferHeaderSize, receivedBuffer.bufferLen * sizeof(float)); // data is a pointer to the beginning of the data + + printf("\ntotal received count: %llu, total data size: %zu, bufferId: %d, bufferType: %s, bufferLen: %d \n", totalReceivedCount, size, receivedBuffer.bufferId, receivedBuffer.bufferType, + receivedBuffer.bufferLen); + + Bela_getDefaultWatcherManager()->tick(totalReceivedCount); + int _id = receivedBuffer.bufferId; + if (_id >= 0 && _id < myVars.size()) { + + for (size_t i = 0; i < receivedBuffer.bufferData.size(); ++i) { + *myVars[_id] = receivedBuffer.bufferData[i]; + } + } + + return true; +} + +bool setup(BelaContext* context, void* userData) { + + Bela_getDefaultWatcherManager()->getGui().setup(context->projectName); + Bela_getDefaultWatcherManager()->setup(context->audioSampleRate); // set sample rate in watcher + + for (int i = 0; i < 2; ++i) { + Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); + } + + Bela_getDefaultWatcherManager()->getGui().setBinaryDataCallback(binaryDataCallback); + + receivedBufferHeaderSize = sizeof(receivedBuffer.bufferId) + sizeof(receivedBuffer.bufferType) + sizeof(receivedBuffer.bufferLen) + sizeof(receivedBuffer.empty); + totalReceivedCount = 0; + Bela_getDefaultWatcherManager()->tick(totalReceivedCount); // init the watcher + + return true; +} + +void render(BelaContext* context, void* userData) { + // DataBuffer& receivedBuffer = + // Bela_getDefaultWatcherManager()->getGui().getDataBuffer(dataBufferId); + // float* data = receivedBuffer.getAsFloat(); +} + +void cleanup(BelaContext* context, void* userData) { +} diff --git a/test/bela-test/render.cpp b/test/bela-test/render.cpp index 04d1c0c..4239971 100644 --- a/test/bela-test/render.cpp +++ b/test/bela-test/render.cpp @@ -3,21 +3,18 @@ Watcher myvar("myvar"); Watcher myvar2("myvar2"); Watcher myvar3("myvar3", WatcherManager::kTimestampSample); Watcher myvar4("myvar4", WatcherManager::kTimestampSample); +Watcher myvar5("myvar5"); + #include #include -float gFrequency = 440.0; -float gPhase; -float gInverseSampleRate; - bool setup(BelaContext *context, void *userData) { Bela_getDefaultWatcherManager()->getGui().setup(context->projectName); Bela_getDefaultWatcherManager()->setup(context->audioSampleRate); // set sample rate in watcher - gInverseSampleRate = 1.0 / context->audioSampleRate; - gPhase = 0.0; + return true; } @@ -45,20 +42,13 @@ void render(BelaContext *context, void *userData) myvar = frames; myvar2 = frames; // log a dense variable densely: good + myvar5 = frames; if(frames % 12 == 0){ // log a sparse variable sparsely: good myvar3 = frames; myvar4 = frames; } - float out = 0.8 * sinf(gPhase); - gPhase += 2.0 * M_PI * gFrequency * gInverseSampleRate; - if(gPhase > 2.0 * M_PI) - gPhase -= 2.0 * M_PI; - - for(unsigned int channel = 0; channel < context->audioOutChannels; channel++) { - audioWrite(context, n, channel, out); - } } } diff --git a/test/readme.md b/test/readme.md index 253e2b5..d443dab 100644 --- a/test/readme.md +++ b/test/readme.md @@ -1,13 +1,13 @@ # testing -pybela has been tested with [Bela](https://github.com/BelaPlatform/Bela) at `dev` branch commit `69cdf75a` and [watcher](https://github.com/BelaPlatform/watcher) at `main` commit `903573a`. +pybela has been tested with [Bela](https://github.com/BelaPlatform/Bela) at `dev` branch commit `d5f0d6f` and [watcher](https://github.com/BelaPlatform/watcher) at `main` commit `903573a`. The watcher code is already included in `bela-test`. You can update your Bela API code following [these instructions](readme.md). To run the tests, copy the `bela-test` code into your Bela, add the `Watcher`` library compile and run it: ```bash -rsync -rvL test/bela-test root@bela.local:Bela/projects/ +rsync -rvL test/bela-test test/bela-test-send root@bela.local:Bela/projects/ ssh root@bela.local "make -C Bela stop Bela PROJECT=bela-test run" ``` @@ -16,3 +16,15 @@ Once the `bela-test` project is running on Bela, you can run the python tests by ```bash python test.py # or `pipenv run python test.py` if you are using a pipenv environment ``` + +You can also test the `bela-test-send` project by running: + +```bash +ssh root@bela.local "make -C Bela stop Bela PROJECT=bela-test run" +``` +and then running the python tests with: + +```bash +python test-send.py # or `pipenv run python test-send.py` if you are using a pipenv environment +``` + \ No newline at end of file diff --git a/test/test-send.py b/test/test-send.py new file mode 100644 index 0000000..3281dfe --- /dev/null +++ b/test/test-send.py @@ -0,0 +1,47 @@ +import unittest +from pybela import Streamer +import struct +import numpy as np +import asyncio + +streamer = Streamer() +variables = ["myvar1", "myvar2"] + + +async def wait(): + await asyncio.sleep(0.1) + +# can't be merged with test.py because in the render.cpp the watcher needs to be 'ticked' when iterating the buffer, not at every audio frame! + +# TOOD test other types (int, double, uint, char) + + +class test_Sender(unittest.TestCase): + def test_send_buffer(self): + if streamer.connect(): + + streamer.start_streaming(variables) + + # Pack the data into binary format + # >I means big-endian unsigned int, 4s means 4-byte string, pad with x for empty bytes + + for id in [0, 1]: + # buffers are only sent from Bela to the host once full, so it needs to be 1024 long to be sent + buffer_id, buffer_type, buffer_length, empty = id, 'f', 1024, 0 + data_list = np.arange(1, buffer_length+1, 1) + streamer.send_buffer(buffer_id, buffer_type, + buffer_length, data_list) + + asyncio.run(wait()) # wait for the buffer to be sent + + for var in variables: + assert np.array_equal( + streamer.streaming_buffers_data[var], data_list), "Data sent and received are not the same" + + streamer.stop_streaming() + + +if __name__ == '__main__': + unittest.main(verbosity=2) + suite = unittest.TestSuite() + suite.addTest(test_Sender('test_send_buffer')) diff --git a/test/test.py b/test/test.py index ccc22b9..b32a490 100644 --- a/test/test.py +++ b/test/test.py @@ -4,8 +4,9 @@ import numpy as np from pybela import Watcher, Streamer, Logger, Monitor, Controller -# all tests should be run with Bela connected and the bela-test project (in test/bela-test) running on the board +os.environ["PYTHONASYNCIODEBUG"] = "1" +# all tests should be run with Bela connected and the bela-test project (in test/bela-test) running on the board class test_Watcher(unittest.TestCase): @@ -94,49 +95,96 @@ def __test_buffers(self, mode): f"{var}_{self.saving_filename}")) def test_start_stop_streaming(self): - async def async_test_start_stop(): - self.streamer.streaming_buffers_queue_length = 1000 - - # delete any existing test files - for var in self.streaming_vars: - remove_file(os.path.join(self.saving_dir, - f"{var}_{self.saving_filename}")) - - # stream with saving - self.streamer.start_streaming(variables=self.streaming_vars, - saving_enabled=True, saving_filename=self.saving_filename, saving_dir=self.saving_dir) - # check streaming mode is FOREVER after start_streaming is called - self.assertEqual(self.streamer._streaming_mode, "FOREVER", - "Streaming mode should be FOREVER after start_streaming") - await asyncio.sleep(0.5) # wait for some data to be streamed - self.streamer.stop_streaming(variables=self.streaming_vars) - # check streaming mode is OFF after stop_streaming - - asyncio.run(async_test_start_stop()) + self.streamer.streaming_buffers_queue_length = 1000 + + # delete any existing test files + for var in self.streaming_vars: + remove_file(os.path.join(self.saving_dir, + f"{var}_{self.saving_filename}")) + + # stream with saving + self.streamer.start_streaming(variables=self.streaming_vars, + saving_enabled=True, saving_filename=self.saving_filename, saving_dir=self.saving_dir) + # check streaming mode is FOREVER after start_streaming is called + self.assertEqual(self.streamer._streaming_mode, "FOREVER", + "Streaming mode should be FOREVER after start_streaming") + asyncio.run(asyncio.sleep(0.5)) # wait for some data to be streamed + self.streamer.stop_streaming(variables=self.streaming_vars) + # check streaming mode is OFF after stop_streaming + self.assertEqual(self.streamer._streaming_mode, "OFF", "Streaming mode should be OFF after stop_streaming") self.__test_buffers(mode="start_stop") def test_scheduling_streaming(self): - async def async_test_scheduling_streaming(): - self.streamer.streaming_buffers_queue_length = 1000 - latest_timestamp = self.streamer.get_latest_timestamp() - sample_rate = self.streamer.sample_rate - timestamps = [latest_timestamp + - sample_rate] * len(self.streaming_vars) # start streaming after ~1s - durations = [sample_rate] * \ - len(self.streaming_vars) # stream for 1s - - self.streamer.schedule_streaming(variables=self.streaming_vars, - timestamps=timestamps, - durations=durations, - saving_enabled=True, - saving_dir=self.saving_dir, - saving_filename=self.saving_filename) - - asyncio.run(async_test_scheduling_streaming()) + self.streamer.streaming_buffers_queue_length = 1000 + latest_timestamp = self.streamer.get_latest_timestamp() + sample_rate = self.streamer.sample_rate + timestamps = [latest_timestamp + + sample_rate] * len(self.streaming_vars) # start streaming after ~1s + durations = [sample_rate] * \ + len(self.streaming_vars) # stream for 1s + + self.streamer.schedule_streaming(variables=self.streaming_vars, + timestamps=timestamps, + durations=durations, + saving_enabled=True, + saving_dir=self.saving_dir, + saving_filename=self.saving_filename) + self.__test_buffers(mode="schedule") + def test_on_buffer_callback(self): + variables = ["myvar", "myvar5"] # dense double + + # test only on vars of the same type + + timestamps = {var: [] for var in variables} + buffers = {var: [] for var in variables} + + def callback(buffer): + timestamps[buffer["name"]].append( + buffer["buffer"]["ref_timestamp"]) + buffers[buffer["name"]].append(buffer["buffer"]["data"]) + + self.streamer.start_streaming( + variables, saving_enabled=False, on_buffer_callback=callback) + + asyncio.run(asyncio.sleep(0.1)) + + self.streamer.stop_streaming(variables) + + for var in variables: + for i in range(1, len(timestamps[var])): + self.assertEqual(timestamps[var][i] - timestamps[var][i-1], 512, + "The timestamps should be continuous. The callback is missing some buffer") + + def test_on_block_callback(self): + variables = ["myvar", "myvar5"] # dense double + + timestamps = {var: [] for var in variables} + buffers = {var: [] for var in variables} + + def callback(block): + for buffer in block: + var = buffer["name"] + timestamps[var].append(buffer["buffer"]["ref_timestamp"]) + buffers[var].append(buffer["buffer"]["data"]) + + self.streamer.start_streaming( + variables, saving_enabled=False, on_block_callback=callback) + + asyncio.run(asyncio.sleep(0.5)) + + self.streamer.stop_streaming(variables) + + self.assertGreater(len(timestamps["myvar"]), 0, "The on_block_callback should have been called at least once") + + for var in variables: + for i in range(1, len(timestamps[var])): + self.assertEqual(timestamps[var][i] - timestamps[var][i-1], 512, + "The timestamps should be continuous. The callback is missing some buffer") + class test_Logger(unittest.TestCase): @@ -181,85 +229,79 @@ def _test_logged_data(self, logger, logging_vars, local_paths): inferred_timestamps, _buffer["data"], "The timestamps should be equal to the ref_timestamp plus the relative timestamps (sparse logging)") def test_logged_files_with_transfer(self): - async def async_test_logged_files_with_transfer(): - - # log with transfer - file_paths = self.logger.start_logging( - variables=self.logging_vars, transfer=True, logging_dir=self.logging_dir) - await asyncio.sleep(0.5) - self.logger.stop_logging() + # log with transfer + file_paths = self.logger.start_logging( + variables=self.logging_vars, transfer=True, logging_dir=self.logging_dir) + asyncio.run(asyncio.sleep(0.5)) + self.logger.stop_logging() - # test logged data - self._test_logged_data(self.logger, self.logging_vars, - file_paths["local_paths"]) + # test logged data + self._test_logged_data(self.logger, self.logging_vars, + file_paths["local_paths"]) - # clean local log files - for var in file_paths["local_paths"]: - remove_file(file_paths["local_paths"][var]) - # clean all remote log files in project - self.logger.delete_all_bin_files_in_project() + # clean local log files + for var in file_paths["local_paths"]: + remove_file(file_paths["local_paths"][var]) + # clean all remote log files in project + self.logger.delete_all_bin_files_in_project() - asyncio.run(async_test_logged_files_with_transfer()) def test_logged_files_wo_transfer(self): - async def async_test_logged_files_wo_transfer(): - - # logging without transfer - file_paths = self.logger.start_logging( - variables=self.logging_vars, transfer=False, logging_dir=self.logging_dir) - await asyncio.sleep(0.5) - self.logger.stop_logging() - - # transfer files from bela - local_paths = {} - for var in file_paths["remote_paths"]: - filename = os.path.basename(file_paths["remote_paths"][var]) - local_paths[var] = self.logger._generate_local_filename( - os.path.join(self.logging_dir, filename)) - self.logger.copy_file_from_bela(remote_path=file_paths["remote_paths"][var], - local_path=local_paths[var]) - - # test logged data - self._test_logged_data(self.logger, self.logging_vars, local_paths) - - # clean log files - for var in self.logging_vars: - remove_file(local_paths[var]) - # self.logger.delete_file_from_bela( - # file_paths["remote_paths"][var]) - self.logger.delete_all_bin_files_in_project() - - asyncio.run(async_test_logged_files_wo_transfer()) + + # logging without transfer + file_paths = self.logger.start_logging( + variables=self.logging_vars, transfer=False, logging_dir=self.logging_dir) + asyncio.run(asyncio.sleep(0.5)) + self.logger.stop_logging() + + # transfer files from bela + local_paths = {} + for var in file_paths["remote_paths"]: + filename = os.path.basename(file_paths["remote_paths"][var]) + local_paths[var] = self.logger._generate_local_filename( + os.path.join(self.logging_dir, filename)) + self.logger.copy_file_from_bela(remote_path=file_paths["remote_paths"][var], + local_path=local_paths[var]) + + # test logged data + self._test_logged_data(self.logger, self.logging_vars, local_paths) + + # clean log files + for var in self.logging_vars: + remove_file(local_paths[var]) + # self.logger.delete_file_from_bela( + # file_paths["remote_paths"][var]) + self.logger.delete_all_bin_files_in_project() + + def test_scheduling_logging(self): - async def async_test_scheduling_logging(): - latest_timestamp = self.logger.get_latest_timestamp() - sample_rate = self.logger.sample_rate - timestamps = [latest_timestamp + - sample_rate] * len(self.logging_vars) # start logging after ~1s - durations = [sample_rate] * len(self.logging_vars) # log for 1s + latest_timestamp = self.logger.get_latest_timestamp() + sample_rate = self.logger.sample_rate + timestamps = [latest_timestamp + + sample_rate] * len(self.logging_vars) # start logging after ~1s + durations = [sample_rate] * len(self.logging_vars) # log for 1s - file_paths = self.logger.schedule_logging(variables=self.logging_vars, - timestamps=timestamps, - durations=durations, - transfer=True, - logging_dir=self.logging_dir) + file_paths = self.logger.schedule_logging(variables=self.logging_vars, + timestamps=timestamps, + durations=durations, + transfer=True, + logging_dir=self.logging_dir) - self._test_logged_data(self.logger, self.logging_vars, - file_paths["local_paths"]) + self._test_logged_data(self.logger, self.logging_vars, + file_paths["local_paths"]) - # clean local log files - for var in file_paths["local_paths"]: - if os.path.exists(file_paths["local_paths"][var]): - os.remove(file_paths["local_paths"][var]) - self.logger.delete_all_bin_files_in_project() + # clean local log files + for var in file_paths["local_paths"]: + if os.path.exists(file_paths["local_paths"][var]): + os.remove(file_paths["local_paths"][var]) + self.logger.delete_all_bin_files_in_project() - # # clean all remote log files in project - # for var in file_paths["remote_paths"]: - # self.logger.delete_file_from_bela( - # file_paths["remote_paths"][var]) + # # clean all remote log files in project + # for var in file_paths["remote_paths"]: + # self.logger.delete_file_from_bela( + # file_paths["remote_paths"][var]) - asyncio.run(async_test_scheduling_logging()) class test_Monitor(unittest.TestCase): @@ -276,81 +318,72 @@ def tearDown(self): self.monitor.__del__() def test_peek(self): - async def async_test_peek(): - peeked_values = self.monitor.peek() # peeks at all variables by default - for var in peeked_values: - self.assertEqual(peeked_values[var]["timestamp"], peeked_values[var]["value"], - "The timestamp of the peeked variable should be equal to the value") - asyncio.run(async_test_peek()) + peeked_values = self.monitor.peek() # peeks at all variables by default + for var in peeked_values: + self.assertEqual(peeked_values[var]["timestamp"], peeked_values[var]["value"], + "The timestamp of the peeked variable should be equal to the value") def test_period_monitor(self): - async def async_test_period_monitor(): - self.monitor.start_monitoring( - variables=self.monitor_vars[:2], - periods=[self.period]*len(self.monitor_vars[:2])) - await asyncio.sleep(0.5) - monitored_values = self.monitor.stop_monitoring() - - for var in self.monitor_vars[:2]: # assigned at every frame n - self.assertTrue(np.all(np.diff(monitored_values[var]["timestamps"]) == self.period), - "The timestamps of the monitored variables should be spaced by the period") - if var in ["myvar", "myvar2"]: # assigned at each frame n - self.assertTrue(np.all(np.diff(monitored_values[var]["values"]) == self.period), - "The values of the monitored variables should be spaced by the period") - - asyncio.run(async_test_period_monitor()) - - def test_monitor_n_values(self): - - async def async_test_monitor_n_values(): - n_values = 25 - monitored_buffer = self.monitor.monitor_n_values( - variables=self.monitor_vars[:2], - periods=[self.period]*len(self.monitor_vars[:2]), n_values=n_values) - - for var in self.monitor_vars[:2]: - self.assertTrue(np.all(np.diff(self.monitor.values[var]["timestamps"]) == self.period), - "The timestamps of the monitored variables should be spaced by the period") - self.assertTrue(np.all(np.diff(self.monitor.values[var]["values"]) == self.period), + self.monitor.start_monitoring( + variables=self.monitor_vars[:2], + periods=[self.period]*len(self.monitor_vars[:2])) + asyncio.run(asyncio.sleep(0.5)) + monitored_values = self.monitor.stop_monitoring() + + for var in self.monitor_vars[:2]: # assigned at every frame n + self.assertTrue(np.all(np.diff(monitored_values[var]["timestamps"]) == self.period), + "The timestamps of the monitored variables should be spaced by the period") + if var in ["myvar", "myvar2"]: # assigned at each frame n + self.assertTrue(np.all(np.diff(monitored_values[var]["values"]) == self.period), "The values of the monitored variables should be spaced by the period") - self.assertTrue(all(len(self.monitor.streaming_buffers_data[ - var]) >= n_values for var in self.monitor_vars[:2]), "The streamed flat buffers for every variable should have at least n_values") - self.assertTrue(all(len(monitored_buffer[ - var]["values"]) == n_values for var in self.monitor_vars[:2]), "The streaming buffers queue should have n_value for every variable") - - asyncio.run(async_test_monitor_n_values()) - - def test_save_monitor(self): - async def async_test_save_monitor(): - # delete any existing test files - for var in self.monitor_vars: - if os.path.exists(f"{var}_{self.saving_filename}"): - os.remove(f"{var}_{self.saving_filename}") - self.monitor.start_monitoring( - variables=self.monitor_vars, - periods=[self.period]*len(self.monitor_vars), - saving_enabled=True, - saving_filename=self.saving_filename, - saving_dir=self.saving_dir) - await asyncio.sleep(0.5) - monitored_buffers = self.monitor.stop_monitoring() + def test_monitor_n_values(self): + n_values = 25 + monitored_buffer = self.monitor.monitor_n_values( + variables=self.monitor_vars[:2], + periods=[self.period]*len(self.monitor_vars[:2]), n_values=n_values) + + for var in self.monitor_vars[:2]: + self.assertTrue(np.all(np.diff(self.monitor.values[var]["timestamps"]) == self.period), + "The timestamps of the monitored variables should be spaced by the period") + self.assertTrue(np.all(np.diff(self.monitor.values[var]["values"]) == self.period), + "The values of the monitored variables should be spaced by the period") + self.assertTrue(all(len(self.monitor.streaming_buffers_data[ + var]) >= n_values for var in self.monitor_vars[:2]), "The streamed flat buffers for every variable should have at least n_values") + self.assertTrue(all(len(monitored_buffer[ + var]["values"]) == n_values for var in self.monitor_vars[:2]), "The streaming buffers queue should have n_value for every variable") - for var in self.monitor_vars: - loaded_buffers = self.monitor.load_data_from_file(os.path.join(self.saving_dir, - f"{var}_{self.saving_filename}")) - self.assertEqual(loaded_buffers["timestamps"], monitored_buffers[var]["timestamps"], - "The timestamps of the loaded buffer should be equal to the timestamps of the monitored buffer") - self.assertEqual(loaded_buffers["values"], monitored_buffers[var]["values"], - "The values of the loaded buffer should be equal to the values of the monitored buffer") + def test_save_monitor(self): - for var in self.monitor_vars: - remove_file(os.path.join(self.saving_dir, - f"{var}_{self.saving_filename}")) + # delete any existing test files + for var in self.monitor_vars: + if os.path.exists(f"{var}_{self.saving_filename}"): + os.remove(f"{var}_{self.saving_filename}") + + self.monitor.start_monitoring( + variables=self.monitor_vars, + periods=[self.period]*len(self.monitor_vars), + saving_enabled=True, + saving_filename=self.saving_filename, + saving_dir=self.saving_dir) + asyncio.run(asyncio.sleep(0.5)) + monitored_buffers = self.monitor.stop_monitoring() + + for var in self.monitor_vars: + loaded_buffers = self.monitor.load_data_from_file(os.path.join(self.saving_dir, + f"{var}_{self.saving_filename}")) + + self.assertEqual(loaded_buffers["timestamps"], monitored_buffers[var]["timestamps"], + "The timestamps of the loaded buffer should be equal to the timestamps of the monitored buffer") + self.assertEqual(loaded_buffers["values"], monitored_buffers[var]["values"], + "The values of the loaded buffer should be equal to the values of the monitored buffer") + + for var in self.monitor_vars: + remove_file(os.path.join(self.saving_dir, + f"{var}_{self.saving_filename}")) - asyncio.run(async_test_save_monitor()) class test_Controller(unittest.TestCase): @@ -364,44 +397,38 @@ def tearDown(self): self.controller.__del__() def test_start_stop_controlling(self): + self.controller.start_controlling(variables=self.controlled_vars) - async def async_test_start_stop_controlling(): - - self.controller.start_controlling(variables=self.controlled_vars) - - self.assertEqual(self.controller.get_controlled_status(variables=self.controlled_vars), { - var: True for var in self.controlled_vars}, "The controlled status of the variables should be True after start_controlling") + self.assertEqual(self.controller.get_controlled_status(variables=self.controlled_vars), { + var: True for var in self.controlled_vars}, "The controlled status of the variables should be True after start_controlling") - self.controller.stop_controlling(variables=self.controlled_vars) + self.controller.stop_controlling(variables=self.controlled_vars) - self.assertEqual(self.controller.get_controlled_status(variables=self.controlled_vars), { - var: False for var in self.controlled_vars}, "The controlled status of the variables should be False after stop_controlling") + self.assertEqual(self.controller.get_controlled_status(variables=self.controlled_vars), { + var: False for var in self.controlled_vars}, "The controlled status of the variables should be False after stop_controlling") - asyncio.run(async_test_start_stop_controlling()) def test_send_value(self): - async def async_test_send_value(): - # TODO add streamer to check values are being sent - self.controller.start_controlling(variables=self.controlled_vars) + # TODO add streamer to check values are being sent + self.controller.start_controlling(variables=self.controlled_vars) - set_value = 4.6 + set_value = 4.6 - self.controller.send_value( - variables=self.controlled_vars, values=[set_value]*len(self.controlled_vars)) - await asyncio.sleep(0.1) # wait for the values to be set + self.controller.send_value( + variables=self.controlled_vars, values=[set_value]*len(self.controlled_vars)) + asyncio.run(asyncio.sleep(0.1)) # wait for the values to be set - _controlled_values = self.controller.get_value( - variables=self.controlled_vars) # avoid multiple calls to list + _controlled_values = self.controller.get_value( + variables=self.controlled_vars) # avoid multiple calls to list - integer_types = ["i", "j"] - expected_values = [int(set_value) if self.controller.get_prop_of_var( - var, "type") in integer_types else set_value for var in self.controlled_vars] + integer_types = ["i", "j"] + expected_values = [int(set_value) if self.controller.get_prop_of_var( + var, "type") in integer_types else set_value for var in self.controlled_vars] - for idx, var in enumerate(self.controlled_vars): - self.assertTrue( - _controlled_values[var] == expected_values[idx], "The controlled value should be 4") + for idx, var in enumerate(self.controlled_vars): + self.assertTrue( + _controlled_values[var] == expected_values[idx], "The controlled value should be 4") - asyncio.run(async_test_send_value()) def remove_file(file_path): @@ -410,8 +437,8 @@ def remove_file(file_path): if __name__ == '__main__': - if 0: - unittest.main(verbosity=2) + # run all tests + # unittest.main(verbosity=2) # select which tests to run n = 1 @@ -419,31 +446,30 @@ def remove_file(file_path): print(f"\n\n....Running test {i+1}/{n}") - if 1: - suite = unittest.TestSuite() - if 1: - suite.addTest(test_Watcher('test_list')) - suite.addTest(test_Watcher('test_start_stop')) - - if 1: - suite.addTest(test_Streamer('test_stream_n_values')) - suite.addTest(test_Streamer('test_start_stop_streaming')) - suite.addTest(test_Streamer('test_scheduling_streaming')) - - if 1: - suite.addTest(test_Logger('test_logged_files_with_transfer')) - suite.addTest(test_Logger('test_logged_files_wo_transfer')) - suite.addTest(test_Logger('test_scheduling_logging')) - - if 1: - suite.addTest(test_Monitor('test_peek')) - suite.addTest(test_Monitor('test_period_monitor')) - suite.addTest(test_Monitor('test_monitor_n_values')) - suite.addTest(test_Monitor('test_save_monitor')) - - if 1: - suite.addTest(test_Controller('test_start_stop_controlling')) - suite.addTest(test_Controller('test_send_value')) - - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite) + suite = unittest.TestSuite() + suite.addTests([ + # watcher + test_Watcher('test_list'), + test_Watcher('test_start_stop'), + # streamer + test_Streamer('test_stream_n_values'), + test_Streamer('test_start_stop_streaming'), + test_Streamer('test_scheduling_streaming'), + test_Streamer('test_on_buffer_callback'), + test_Streamer('test_on_block_callback'), + # logger + test_Logger('test_logged_files_with_transfer'), + test_Logger('test_logged_files_wo_transfer'), + test_Logger('test_scheduling_logging'), + # monitor + test_Monitor('test_peek'), + test_Monitor('test_period_monitor'), + test_Monitor('test_monitor_n_values'), + test_Monitor('test_save_monitor'), + # controller + test_Controller('test_start_stop_controlling'), + test_Controller('test_send_value') + ]) + # suite.addTest(test_Streamer('test_on_block_callback')) + runner = unittest.TextTestRunner(verbosity=2) + runner.run(suite) diff --git a/tutorials/bela-code/bela2python2bela/Watcher.cpp b/tutorials/bela-code/bela2python2bela/Watcher.cpp new file mode 120000 index 0000000..9477c2f --- /dev/null +++ b/tutorials/bela-code/bela2python2bela/Watcher.cpp @@ -0,0 +1 @@ +../../../watcher/Watcher.cpp \ No newline at end of file diff --git a/tutorials/bela-code/bela2python2bela/Watcher.h b/tutorials/bela-code/bela2python2bela/Watcher.h new file mode 120000 index 0000000..059dbc5 --- /dev/null +++ b/tutorials/bela-code/bela2python2bela/Watcher.h @@ -0,0 +1 @@ +../../../watcher/Watcher.h \ No newline at end of file diff --git a/tutorials/bela-code/bela2python2bela/render.cpp b/tutorials/bela-code/bela2python2bela/render.cpp new file mode 100644 index 0000000..fc8a02d --- /dev/null +++ b/tutorials/bela-code/bela2python2bela/render.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include + +#define NUM_OUTPUTS 2 +#define MAX_EXPECTED_BUFFER_SIZE 1024 + +Watcher pot1("pot1"); +Watcher pot2("pot2"); + +uint gPot1Ch = 0; +uint gPot2Ch = 1; + +std::vector> circularBuffers(NUM_OUTPUTS); + +size_t circularBufferSize = 30 * 1024; +size_t prefillSize = 2.5 * 1024; +uint32_t circularBufferWriteIndex[NUM_OUTPUTS] = {0}; +uint32_t circularBufferReadIndex[NUM_OUTPUTS] = {0}; + +struct ReceivedBuffer { + uint32_t bufferId; + char bufferType[4]; + uint32_t bufferLen; + uint32_t empty; + std::vector bufferData; +}; +ReceivedBuffer receivedBuffer; +uint receivedBufferHeaderSize; +uint64_t totalReceivedCount; // total number of received buffers + +unsigned int gAudioFramesPerAnalogFrame; +float gInvAudioFramesPerAnalogFrame; +float gInverseSampleRate; +float gPhase1; +float gPhase2; +float gFrequency1 = 440.0f; +float gFrequency2 = 880.0f; + +// this callback is called every time a buffer is received from python. it parses the received data into the ReceivedBuffer struct, and then writes the data to the circular buffer which is read in the +// render function +bool binaryDataCallback(const std::string& addr, const WSServerDetails* id, const unsigned char* data, size_t size, void* arg) { + + if (totalReceivedCount == 0) { + RtThread::setThisThreadPriority(1); + } + + totalReceivedCount++; + + // parse buffer header + std::memcpy(&receivedBuffer, data, receivedBufferHeaderSize); + receivedBuffer.bufferData.resize(receivedBuffer.bufferLen); + // parse buffer data + std::memcpy(receivedBuffer.bufferData.data(), data + receivedBufferHeaderSize, receivedBuffer.bufferLen * sizeof(float)); + + // write the data onto the circular buffer + int _id = receivedBuffer.bufferId; + if (_id >= 0 && _id < NUM_OUTPUTS) { + for (size_t i = 0; i < receivedBuffer.bufferLen; ++i) { + circularBuffers[_id][circularBufferWriteIndex[_id]] = receivedBuffer.bufferData[i]; + circularBufferWriteIndex[_id] = (circularBufferWriteIndex[_id] + 1) % circularBufferSize; + } + } + + return true; +} + +bool setup(BelaContext* context, void* userData) { + + Bela_getDefaultWatcherManager()->getGui().setup(context->projectName); + Bela_getDefaultWatcherManager()->setup(context->audioSampleRate); // set sample rate in watcher + + gAudioFramesPerAnalogFrame = context->audioFrames / context->analogFrames; + gInvAudioFramesPerAnalogFrame = 1.0 / gAudioFramesPerAnalogFrame; + gInverseSampleRate = 1.0 / context->audioSampleRate; + + // initialize the Gui buffers and circular buffers + for (int i = 0; i < NUM_OUTPUTS; ++i) { + Bela_getDefaultWatcherManager()->getGui().setBuffer('f', MAX_EXPECTED_BUFFER_SIZE); + circularBuffers[i].resize(circularBufferSize, 0.0f); + // the write index is given some "advantage" (prefillSize) so that the read pointer does not catch up the write pointer + circularBufferWriteIndex[i] = prefillSize % circularBufferSize; + } + + Bela_getDefaultWatcherManager()->getGui().setBinaryDataCallback(binaryDataCallback); + + // vars and preparation for parsing the received buffer + receivedBufferHeaderSize = sizeof(receivedBuffer.bufferId) + sizeof(receivedBuffer.bufferType) + sizeof(receivedBuffer.bufferLen) + sizeof(receivedBuffer.empty); + totalReceivedCount = 0; + receivedBuffer.bufferData.reserve(MAX_EXPECTED_BUFFER_SIZE); + + return true; +} + +void render(BelaContext* context, void* userData) { + for (unsigned int n = 0; n < context->audioFrames; n++) { + uint64_t frames = context->audioFramesElapsed + n; + + if (gAudioFramesPerAnalogFrame && !(n % gAudioFramesPerAnalogFrame)) { + Bela_getDefaultWatcherManager()->tick(frames * gInvAudioFramesPerAnalogFrame); // watcher timestamps + + // read sensor values and put them in the watcher + pot1 = analogRead(context, n / gAudioFramesPerAnalogFrame, gPot1Ch); + pot2 = analogRead(context, n / gAudioFramesPerAnalogFrame, gPot2Ch); + + // read the values sent from python (they're in the circular buffer) + for (unsigned int i = 0; i < NUM_OUTPUTS; i++) { + + if (totalReceivedCount > 0 && (circularBufferReadIndex[i] + 1) % circularBufferSize != circularBufferWriteIndex[i]) { + circularBufferReadIndex[i] = (circularBufferReadIndex[i] + 1) % circularBufferSize; + } else if (totalReceivedCount > 0) { + rt_printf("The read pointer has caught the write pointer up in buffer %d – try increasing prefillSize\n", i); + } + } + } + float amp1 = circularBuffers[0][circularBufferReadIndex[0]]; + float amp2 = circularBuffers[1][circularBufferReadIndex[1]]; + + float out = amp1 * sinf(gPhase1) + amp2 * sinf(gPhase2); + + for (unsigned int channel = 0; channel < context->audioOutChannels; channel++) { + audioWrite(context, n, channel, out); + } + + gPhase1 += 2.0f * (float)M_PI * gFrequency1 * gInverseSampleRate; + if (gPhase1 > M_PI) + gPhase1 -= 2.0f * (float)M_PI; + gPhase2 += 2.0f * (float)M_PI * gFrequency2 * gInverseSampleRate; + if (gPhase2 > M_PI) + gPhase2 -= 2.0f * (float)M_PI; + } +} + +void cleanup(BelaContext* context, void* userData) { +} \ No newline at end of file diff --git a/tutorials/bela-code/potentiometers/sketch.js b/tutorials/bela-code/potentiometers/sketch.js deleted file mode 120000 index 0e717d7..0000000 --- a/tutorials/bela-code/potentiometers/sketch.js +++ /dev/null @@ -1 +0,0 @@ -../../../watcher/sketch.js \ No newline at end of file diff --git a/tutorials/bela-code/timestamping/sketch.js b/tutorials/bela-code/timestamping/sketch.js deleted file mode 120000 index 0e717d7..0000000 --- a/tutorials/bela-code/timestamping/sketch.js +++ /dev/null @@ -1 +0,0 @@ -../../../watcher/sketch.js \ No newline at end of file diff --git a/tutorials/notebooks/1_Streamer.ipynb b/tutorials/notebooks/1_Streamer-Bela-to-python-basics.ipynb similarity index 88% rename from tutorials/notebooks/1_Streamer.ipynb rename to tutorials/notebooks/1_Streamer-Bela-to-python-basics.ipynb index 99da975..09ac92c 100644 --- a/tutorials/notebooks/1_Streamer.ipynb +++ b/tutorials/notebooks/1_Streamer-Bela-to-python-basics.ipynb @@ -4,8 +4,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela Tutorial 1: Streamer\n", - "This notebook is a tutorial for the Streamer class in the pybela python library. You can use the Streamer to stream data from Bela to python. The Streamer allows you to start and stop streaming, to stream a given number of data points, to plot the data as it arrives, and to save and load the streamed data into `.txt` files. \n", + "# pybela Tutorial 1: Streamer – Bela to python basics\n", + "This notebook is a tutorial for the Streamer class in the pybela python library. You can use the Streamer to stream data from Bela to python or vice versa. \n", + "\n", + "In this tutorial we will be looking at sending data from Bela to python. The Streamer allows you to start and stop streaming, to stream a given number of data points, to plot the data as it arrives, and to save and load the streamed data into `.txt` files. \n", + "\n", + "The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n", "\n", "To run this tutorial, first copy the `bela-code/potentiometers` project onto Bela. If your Bela is connected to your laptop, you can run the cell below:" ] @@ -35,7 +39,9 @@ "metadata": {}, "source": [ "### Setting up the circuit\n", - "The Bela code expects two potentiometers connected to analog inputs 0 and 1. Potentiometers have 3 pins. To connect a potentiometer to Bela, attach the left pin to the Bela 3.3V pin, the central pin to the desired analog input (e.g. 0) and the right pin to the Bela GND pin:\n", + "In this example we will be using two potentiometers as our analog signals, but you can connect whichever sensors you like to analog channels 0 and 1.\n", + "\n", + "Potentiometers have 3 pins. To connect a potentiometer to Bela, attach the left pin to the Bela 3.3V pin, the central pin to the desired analog input (e.g. 0) and the right pin to the Bela GND pin:\n", "\n", "

\n", "\n", @@ -87,7 +93,10 @@ "source": [ "import asyncio\n", "import pandas as pd\n", - "from pybela import Streamer" + "from pybela import Streamer\n", + "import os\n", + "# os.environ['BOKEH_ALLOW_WS_ORIGIN'] = \"1t4j54lsdj67h02ol8hionopt4k7b7ngd9483l5q5pagr3j2droq\" # uncomment if running on vscode\n", + "os.environ['BOKEH_ALLOW_WS_ORIGIN'] = \"localhost:8888\" # uncomment if running on jupyter" ] }, { @@ -263,7 +272,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "More advanced timestamping methods will be shown in the tutorial notebook `4_Sparse_timestamping.ipynb`\n", + "More advanced timestamping methods will be shown in the tutorial notebook `7_Sparse_timestamping.ipynb`\n", "\n", "There is a limited amount of data that is stored in the streamer. This quantity can be modified by changing the buffer queue length. The streamer receives the data in buffers of fixed length that get stored in a queue that also has a fixed length. You can calculate the maximum amount of data the streamer can store for each variable:\n", "\n", @@ -298,42 +307,6 @@ "streamer.streaming_buffers_queue_length = 10" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Streaming a fixed number of values\n", - "Alternatively, you can stream a fixed number of values of a variable using `stream_n_values()`. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "n_values = 1000\n", - "streaming_buffer = streamer.stream_n_values(\n", - " variables=[var[\"name\"] for var in streamer.watcher_vars], n_values=n_values)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Since the data buffers received from Bela have a fixed size, unless the number of values `n_values` is a multiple of the data buffers size, the streamer will always return a few more values than asked for." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for var in streamer.watcher_vars:\n", - " print(f'Variable: {var[\"name\"]}, buffer length: {var[\"data_length\"]}, number of streamed values: {len(streamer.streaming_buffers_data[var[\"name\"]])}')" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -389,7 +362,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.9.16" } }, "nbformat": 4, diff --git a/tutorials/notebooks/2_Streamer-Bela-to-python-advanced.ipynb b/tutorials/notebooks/2_Streamer-Bela-to-python-advanced.ipynb new file mode 100644 index 0000000..342c708 --- /dev/null +++ b/tutorials/notebooks/2_Streamer-Bela-to-python-advanced.ipynb @@ -0,0 +1,208 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# pybela Tutorial 2: Streamer – Bela to python advanced\n", + "This notebook is a tutorial for the Streamer class in the pybela python library. You can use the Streamer to stream data from Bela to python or vice versa. \n", + "\n", + "In this tutorial we will be looking at more advanced features to send data from Bela to python. \n", + "\n", + "The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n", + "\n", + "If you didn't do it in the previous tutorial, copy the `bela-code/python-to-bela` project onto Bela. If your Bela is connected to your laptop, you can run the cell below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!rsync -rvL ../bela-code/python-to-bela root@bela.local:Bela/projects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then you can compile and run the project using either the IDE or by running the following command in the Terminal:\n", + "```bash\n", + "ssh root@bela.local \"make -C Bela stop Bela PROJECT=python-to-bela run\" \n", + "```\n", + "(Running this on a jupyter notebook will block the cell until the program is stopped on Bela.) You will also need to connect two potentiometers to Bela analog inputs 0 and 1. Instructions on how to do so and some details on the Bela code are given in the notebook `1_Streamer-Bela-to-python-basics.ipynb`.\n", + "\n", + "First, we need to import the pybela library, create a Streamer object and connect to Bela." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "from pybela import Streamer\n", + "\n", + "streamer = Streamer()\n", + "streamer.connect()\n", + "\n", + "variables = [\"pot1\", \"pot2\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Streaming a fixed number of values\n", + "You can can use the method `stream_n_values` to stream a fixed number of values of a variable. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "n_values = 1000\n", + "streaming_buffer = streamer.stream_n_values(\n", + " variables=[var[\"name\"] for var in variables], n_values=n_values)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since the data buffers received from Bela have a fixed size, unless the number of values `n_values` is a multiple of the data buffers size, the streamer will always return a few more values than asked for." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for var in variables:\n", + " print(f'Variable: {var[\"name\"]}, buffer length: {var[\"data_length\"]}, number of streamed values: {len(streamer.streaming_buffers_data[var[\"name\"]])}')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Scheduling streaming sessions\n", + "You can schedule a streaming session to start and stop at a specific time using the `schedule_streaming()` method. This method takes the same arguments as `start_streaming()`, but it also takes a `timestamps` and `durations` argument." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "latest_timestamp = streamer.get_latest_timestamp() # get the latest timestamp\n", + "sample_rate = streamer.sample_rate # get the sample rate\n", + "start_timestamp = latest_timestamp + sample_rate # start streaming 1 second after the latest timestamp\n", + "duration = sample_rate # stream for 2 seconds\n", + "\n", + "streamer.schedule_streaming(\n", + " variables=variables,\n", + " timestamps=[start_timestamp, start_timestamp],\n", + " durations=[duration, duration],\n", + " saving_enabled=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### On-buffer and on-block callbacks\n", + "Up until now, we have been streaming data for a period of time and processed the data once the streaming has finished. However, you can also process the data as it is being received. You can do this by passing a callback function to the `on_buffer` or `on_block` arguments of the `start_streaming()` method. \n", + "\n", + "The `on_buffer` callback will be called every time a buffer is received from Bela. We will need to define a callback function that takes one argument, the buffer. The Streamer will call that function every time it receives a buffer. You can also pass variables to the callback function by using the `callback_args` argument of the `start_streaming()` method. Let's see an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "timestamps = {var: [] for var in variables}\n", + "buffers = {var: [] for var in variables}\n", + "\n", + "def callback(buffer, timestamps, buffers):\n", + " print(\"Buffer received\")\n", + " \n", + " _var = buffer[\"name\"]\n", + " timestamps[_var].append(\n", + " buffer[\"buffer\"][\"ref_timestamp\"])\n", + " buffers[_var].append(buffer[\"buffer\"][\"data\"])\n", + " \n", + " print(_var, timestamps[_var][-1])\n", + "\n", + "streamer.start_streaming(\n", + " variables, saving_enabled=False, on_buffer_callback=callback, callback_args=(timestamps, buffers))\n", + "\n", + "await asyncio.sleep(2)\n", + "streamer.stop_streaming()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now look at the `on_block`callback. We call block to a group of buffers. If you are streaming two variables, `pot1` and `pot2`, a block of buffers will contain a buffer for `pot1` and a buffer for `pot2`. If `pot1` and `pot2` have the same buffer size and they are being streamed at the same rate, `pot1` and `pot2` will be aligned in time. This is useful if you are streaming multiple variables and you want to process them together. \n", + "\n", + "The `on_block` callback will be called every time a block of buffers is received from Bela. We will need to define a callback function that takes one argument, the block. The Streamer will call that function every time it receives a block of buffers. Let's see an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "timestamps = {var: [] for var in variables}\n", + "buffers = {var: [] for var in variables}\n", + "\n", + "def callback(block, timestamps, buffers):\n", + " print(\"Block received\")\n", + " \n", + " for buffer in block:\n", + " var = buffer[\"name\"]\n", + " timestamps[var].append(buffer[\"buffer\"][\"ref_timestamp\"])\n", + " buffers[var].append(buffer[\"buffer\"][\"data\"])\n", + "\n", + " print(var, timestamps[var][-1])\n", + " \n", + "streamer.start_streaming(\n", + " variables, saving_enabled=False, on_block_callback=callback, callback_args=(timestamps, buffers))\n", + "await asyncio.sleep(2)\n", + "streamer.stop_streaming()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "pybela-2uXYSGIe", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/notebooks/3_Streamer-python-to-Bela.ipynb b/tutorials/notebooks/3_Streamer-python-to-Bela.ipynb new file mode 100644 index 0000000..387622e --- /dev/null +++ b/tutorials/notebooks/3_Streamer-python-to-Bela.ipynb @@ -0,0 +1,245 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# pybela Tutorial 3: Streamer – python to Bela\n", + "This notebook is a tutorial for the Streamer class in the pybela python library. You can use the Streamer to stream data from Bela to python or viceversa. The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n", + "\n", + "In this tutorial we will be looking at sending data from python to Bela. There is only one method available in the Streamer class for this purpose: `send_buffer()`. This method sends a buffer of a certain type and size to Bela. \n", + "\n", + "To run this tutorial, first copy the `bela-code/bela2python2bela` project onto Bela. If your Bela is connected to your laptop, you can run the cell below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!rsync -rvL ../bela-code/bela2python2bela root@bela.local:Bela/projects" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then you can compile and run the project using either the IDE or by running the following command in the Terminal:\n", + "```bash\n", + "ssh root@bela.local \"make -C Bela stop Bela PROJECT=bela2python2bela run\" \n", + "```\n", + "(Running this on a jupyter notebook will block the cell until the program is stopped on Bela.) \n", + "\n", + "This program expects two analog signals in channels 0 and 1, you can keep using the potentiometer setup from the previous tutorials (check the schematic in `1_Streamer-Bela-to-python.ipynb`)\n", + "\n", + "In this example we will be sending the values of the two potentiometers from Bela to python. Once received in python, we will send them immediately back to Bela. The values received in Bela will be used to modulate the amplitude of two sine waves. It is admittedly an overly complicated way to modulate two sine waves in Bela, as you could of course use the potentiometer values directly, without having to send them to python and back. However, this example can serve as a template for more complex applications where you can process the data in python before sending it back to Bela. \n", + "\n", + "## Understanding the Bela code\n", + "If you are not familiar with auxiliary tasks and circular buffers, we recommend you follow first [Lesson 11](https://youtu.be/xQBftd7WNY8?si=ns6ojYnfQ_GVtCQI) and [Lesson 17](https://youtu.be/2uyWn8P0CVg?si=Ymy-NN_HKS-Q3xL0) of the C++ Real-Time Audio Programming with Bela course. \n", + "\n", + "Let's first take a look at the Bela code. The `setup()` function initializes the Bela program and some necessary variables. First, we set up the Watcher with the `Bela_getDefaultWatcherManager()` function. We then calculate the inverse of some useful variables (multiplying by the inverse is faster than dividing, so we precompute the inverse in `setup` and use it later in `render`). We then initialize the GUI buffers (these are the internal buffers Bela uses to receive the data) and the `circularBuffers`. The `circularBuffers` are used to store the parsed data from the GUI buffers, and are the variables we will use in `render` to access the data we have sent from python. We also set up the `binaryDataCallback` function, which will be called when Bela receives a buffer from python. \n", + "\n", + "\n", + "```cpp\n", + "bool setup(BelaContext* context, void* userData) {\n", + "\n", + " Bela_getDefaultWatcherManager()->getGui().setup(context->projectName);\n", + " Bela_getDefaultWatcherManager()->setup(context->audioSampleRate); // set sample rate in watcher\n", + "\n", + " gAudioFramesPerAnalogFrame = context->audioFrames / context->analogFrames;\n", + " gInvAudioFramesPerAnalogFrame = 1.0 / gAudioFramesPerAnalogFrame;\n", + " gInverseSampleRate = 1.0 / context->audioSampleRate;\n", + "\n", + " // initialize the Gui buffers and circular buffers\n", + " for (int i = 0; i < NUM_OUTPUTS; ++i) {\n", + " Bela_getDefaultWatcherManager()->getGui().setBuffer('f', MAX_EXPECTED_BUFFER_SIZE);\n", + " circularBuffers[i].resize(circularBufferSize, 0.0f);\n", + " // the write index is given some \"advantage\" (prefillSize) so that the read pointer does not catch up the write pointer\n", + " circularBufferWriteIndex[i] = prefillSize % circularBufferSize;\n", + " }\n", + "\n", + " Bela_getDefaultWatcherManager()->getGui().setBinaryDataCallback(binaryDataCallback);\n", + "\n", + " // vars and preparation for parsing the received buffer\n", + " receivedBufferHeaderSize = sizeof(receivedBuffer.bufferId) + sizeof(receivedBuffer.bufferType) + sizeof(receivedBuffer.bufferLen) + sizeof(receivedBuffer.empty);\n", + " totalReceivedCount = 0;\n", + " receivedBuffer.bufferData.reserve(MAX_EXPECTED_BUFFER_SIZE);\n", + "\n", + " return true;\n", + "}\n", + "```\n", + "\n", + "Let's now take a look at the `render()` function. The render function is called once per audio block, so inside of it we iterate over the audio blocks. Since the potentiometers are analog signals, and in Bela the analog inputs are typically sampled at a lower rate than the audio, we read the potentiometers once every 2 audio frames (in the code, `gAudioFramesPerAnalogFrame` is equal to 2 if you are using the default 8 audio channels). Since the variables `pot1` and `pot2` are in the Watcher, these will be streamed to python if we run `start_streaming()` in python.\n", + "\n", + "Next, we check if the variable `totalReceivedCount` is greater than 0, which means that we have received at least a buffer from python. If we have received buffers and the read pointer has not caught up with the write pointer, we advance the read pointer in the circular buffer. The reason why we check if we have received a buffer first, is because we don't want to advance the read pointer if we haven't received any data yet, as then the read pointer would catch up with the write pointer. \n", + "\n", + "Finally, we read the values from the circular buffer and use them to modulate the amplitude of two sine waves. We then write the output to the audio channels.\n", + "\n", + "\n", + "\n", + "```cpp\n", + "\n", + "void render(BelaContext* context, void* userData) {\n", + " for (unsigned int n = 0; n < context->audioFrames; n++) {\n", + " uint64_t frames = context->audioFramesElapsed + n;\n", + "\n", + " if (gAudioFramesPerAnalogFrame && !(n % gAudioFramesPerAnalogFrame)) {\n", + " Bela_getDefaultWatcherManager()->tick(frames * gInvAudioFramesPerAnalogFrame); // watcher timestamps\n", + "\n", + " // read sensor values and put them in the watcher\n", + " pot1 = analogRead(context, n / gAudioFramesPerAnalogFrame, gPot1Ch);\n", + " pot2 = analogRead(context, n / gAudioFramesPerAnalogFrame, gPot2Ch);\n", + "\n", + " // read the values sent from python (they're in the circular buffer)\n", + " for (unsigned int i = 0; i < NUM_OUTPUTS; i++) {\n", + "\n", + " if (totalReceivedCount > 0 && (circularBufferReadIndex[i] + 1) % circularBufferSize != circularBufferWriteIndex[i]) {\n", + " circularBufferReadIndex[i] = (circularBufferReadIndex[i] + 1) % circularBufferSize;\n", + " } else if (totalReceivedCount > 0) {\n", + " rt_printf(\"The read pointer has caught the write pointer up in buffer %d – try increasing prefillSize\\n\", i);\n", + " }\n", + " }\n", + " }\n", + "\n", + " float amp1 = circularBuffers[0][circularBufferReadIndex[0]];\n", + " float amp2 = circularBuffers[1][circularBufferReadIndex[1]];\n", + "\n", + " float out = amp1 * sinf(gPhase1) + amp2 * sinf(gPhase2);\n", + "\n", + " for (unsigned int channel = 0; channel < context->audioOutChannels; channel++) {\n", + " audioWrite(context, n, channel, out);\n", + " }\n", + "\n", + " gPhase1 += 2.0f * (float)M_PI * gFrequency1 * gInverseSampleRate;\n", + " if (gPhase1 > M_PI)\n", + " gPhase1 -= 2.0f * (float)M_PI;\n", + " gPhase2 += 2.0f * (float)M_PI * gFrequency2 * gInverseSampleRate;\n", + " if (gPhase2 > M_PI)\n", + " gPhase2 -= 2.0f * (float)M_PI;\n", + "\n", + " }\n", + "}\n", + "```\n", + "\n", + "Let's now run the python code:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from pybela import Streamer\n", + "streamer = Streamer()\n", + "streamer.connect()\n", + "\n", + "variables = [\"pot1\", \"pot2\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `send_buffer` function takes 4 arguments: the buffer id, the type of the data that goes in the buffer, the buffer length and the buffer data. Since we will be sending back the buffers we receive from Bela, we can get the type and length of the buffer through the streamer:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "buffer_type = streamer.get_prop_of_var(\"pot1\", \"type\")\n", + "buffer_length = streamer.get_prop_of_var(\"pot1\", \"data_length\")\n", + "\n", + "buffer_type, buffer_length\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here we will be using the `block_callback` instead of the `buffer_callback`, as the `block` callback is more efficient. It should be noted that we are receiving and sending blocks of data every 1024/22050 = 0.05 seconds, and the maximum latency is given by the `prefillSize` variable in the Bela code (which is set to 2.5*1024/22050 = 0.12 seconds), so using functions is crucial to meet the real-time deadlines." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def callback(block):\n", + " \n", + " for buffer in block:\n", + " \n", + " _var = buffer[\"name\"]\n", + " timestamp = buffer[\"buffer\"][\"ref_timestamp\"]\n", + " data = buffer[\"buffer\"][\"data\"]\n", + " \n", + " buffer_id = 0 if _var == \"pot1\" else 1\n", + "\n", + " print(buffer_id, timestamp)\n", + " # do some data processing here...\n", + " processed_data = data\n", + " \n", + " # send processed_data back\n", + " streamer.send_buffer(buffer_id, buffer_type,\n", + " buffer_length, processed_data)\n", + "\n", + "streamer.start_streaming(\n", + " variables, saving_enabled=False, on_block_callback=callback)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you plug in your headphones to the audio output of Bela, you should hear two sine waves modulated by the potentiometers. The modulation (the amplitude change) is given by the value sent by python, not the analog input directly on Bela. As mentioned before, this is an overly complicated way to modulate two sine waves, but it can serve as a template for more complex applications where you can process the data in python before sending it back to Bela." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "streamer.stop_streaming()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/tutorials/notebooks/2_Monitor.ipynb b/tutorials/notebooks/4_Monitor.ipynb similarity index 96% rename from tutorials/notebooks/2_Monitor.ipynb rename to tutorials/notebooks/4_Monitor.ipynb index c70b139..5602d99 100644 --- a/tutorials/notebooks/2_Monitor.ipynb +++ b/tutorials/notebooks/4_Monitor.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela tutorial 2: Monitor\n", + "# pybela tutorial 4: Monitor\n", "This tutorial expects the `potentiometers` project to be running on Bela. If the Bela is connected to your laptop, you can run the cell below to copy the `potentiometers` code with the `Watcher` library onto your Bela:" ] }, @@ -25,14 +25,9 @@ "```bash\n", "ssh root@bela.local \"make -C Bela stop Bela PROJECT=potentiometers run\" \n", "```\n", - "(Running this on a jupyter notebook will block the cell until the program is stopped on Bela.)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You will also need to connect two potentiometers to Bela analog inputs 0 and 1. Instructions on how to do so and some details on the Bela code are given in the notebook `1_Streamer.ipynb`.\n", + "(Running this on a jupyter notebook will block the cell until the program is stopped on Bela.)\n", + "\n", + "You will also need to connect two potentiometers to Bela analog inputs 0 and 1. Instructions on how to do so and some details on the Bela code are given in the notebook `1_Streamer-Bela-to-python-basics.ipynb`. The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n", "\n", "This notebook is a tutorial for the Monitor class in the pybela python library. The monitor allows you to \"take a look\" at variables in your Bela code. By taking a look we mean either requesting a single value (*what value does `pot1` have right now?*) or sampling the value of a variable, that is, getting a value every number of frames (*can you tell me the value of `pot1` every 1000 frames?*). The monitor can be useful to calibrate sensors or, in general, debug your Bela code. \n", "\n", diff --git a/tutorials/notebooks/4_Sparse_timestamping.ipynb b/tutorials/notebooks/4_Sparse_timestamping.ipynb deleted file mode 100644 index 48b0b15..0000000 --- a/tutorials/notebooks/4_Sparse_timestamping.ipynb +++ /dev/null @@ -1 +0,0 @@ -{"cells":[{"cell_type":"markdown","metadata":{},"source":["# pybela Tutorial 4: Sparse timestamping\n","In the potentiometer example used in the previous tutorials, the values for `pot1` and `pot2` are assigned at every audio frame. Let's take a look again at the `render()` loop (the Bela code for this example can be found in (in `bela-code/potentiometers/render.cpp`).\n","\n","```cpp\n","void render(BelaContext *context, void *userData)\n","{\n","\tfor(unsigned int n = 0; n < context->audioFrames; n++) {\n","\t\tif(gAudioFramesPerAnalogFrame && !(n % gAudioFramesPerAnalogFrame)) {\n","\t\t\t\n","\t\t\tuint64_t frames = context->audioFramesElapsed/gAudioFramesPerAnalogFrame + n/gAudioFramesPerAnalogFrame;\n","\t\t\tBela_getDefaultWatcherManager()->tick(frames); // watcher timestamps\n","\t\t\t\n","\t\t\tpot1 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot1Ch);\n","\t\t\tpot2 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot2Ch);\n","\t\t\t\n","\t\t}\n","\t}\n","}\n","```\n","\n","\n","The Watched clock is also \"ticked\" at every analog frame, so that the timestamps in the data correspond to the audio frames in the Bela code. The data buffers we received from Bela in the Streamer and the Logger had this form: `{\"ref_timestamp\": 92381, \"data\":[0.34, 0.45, ...]}`. Each data point is registered in the buffer every time we assign a value to `pot1` and `pot2` in the Bela code. The `ref_timestamp` corresponds to the timestamp of the first sample in the `data` array, in this case `0.34`. Since in the Bela code, we assign `pot1` and `pot2` at every audio frame, we can infer the timestamps of each value in the data array by incrementing `ref_timestamp` by 1 for each sample. \n","\n","This is an efficient way of storing data since instead of storing the timestamp of every item in the data array, we only store the timestamp of the first item. We call this *dense* timestamping. However, for many applications, we might not assign a value to a variable every frame, we might do it more than once per frame, once every few frames, or we might want to do it at irregular intervals. In these cases, we need to store the timestamp of every item in the data array. We call this *sparse* timestamping.\n","\n","In this tutorial we take a look at *sparse* timestamping. First, transfer the Bela code we will use in this tutorial to Bela:\n"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["!rsync -rvL ../bela-code/timestamping root@bela.local:Bela/projects"]},{"cell_type":"markdown","metadata":{},"source":["Then you can compile and run the project using either the IDE or by running the following command in the Terminal:\n","```bash\n","ssh root@bela.local \"make -C Bela stop Bela PROJECT=potentiometers run\" \n","```\n","(Running this on a jupyter notebook will block the cell until the program is stopped on Bela.)"]},{"cell_type":"markdown","metadata":{},"source":["As in the previous tutorials, we will use two potentiometers connected to Bela analog inputs 0 and 1. Check the `1_Streamer.ipnyb` tutorial notebook for instructions on how to set up the circuit. \n","\n","### Bela C++ code\n","\n","\n","First, let's take a look at the Bela code. First, we have added `WatcherManager::kTimestampSample` to the declaration of `pot2`. This informs the Bela Watcher that `pot2` will be watched sparsely, that is, that the watcher will store a timestamp for every value assigned to `pot2`:\n","\n","```cpp\n","Watcher pot1(\"pot1\");\n","Watcher pot2(\"pot2\", WatcherManager::kTimestampSample);\n","```\n","\n","Now let's take a look at `render()`:\n","\n","```cpp\n","void render(BelaContext *context, void *userData)\n","{\n","\tfor(unsigned int n = 0; n < context->audioFrames; n++) {\n","\t\tif(gAudioFramesPerAnalogFrame && !(n % gAudioFramesPerAnalogFrame)) {\n","\t\t\t\n","\t\t\tuint64_t frames = context->audioFramesElapsed/gAudioFramesPerAnalogFrame + n/gAudioFramesPerAnalogFrame;\n","\t\t\tBela_getDefaultWatcherManager()->tick(frames); // watcher timestamps\n","\t\t\t\n","\t\t\tpot1 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot1Ch);\n","\n","\t\t\tif (frames % 12==0){\n","\t\t\t\tpot2 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot2Ch);\n","\t\t\t}\n","\t\t}\n","\t}\n","}\n","```\n","\n","We are \"ticking\" the Bela Watcher once per analog frame, so that the timestamps in the data correspond to the analog frames in the Bela code. We are assigning a value to `pot1` at every analog frame, as in the previous examples, but we are now only assigning a value to `pot2` every 12 frames. \n","\n","### Dealing with sparse timestamps in Python\n","\n","Let's now take a look at the data we receive from Bela. We will use the Streamer. Run the cells below to declare and connect the Streamer to Bela:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["import asyncio\n","import pandas as pd\n","from pybela import Streamer"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer = Streamer()\n","streamer.connect()"]},{"cell_type":"markdown","metadata":{},"source":["We can call `.list()` to take a look at the variables available to be streamed, their types and timestamp mode:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer.list()"]},{"cell_type":"markdown","metadata":{},"source":["`timestampMode` indicates if the timestamping is *sparse* (1) or *dense* (0). Now let's stream the data from Bela. We will stream `pot1` and `pot2`:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer.start_streaming(variables=[\"pot1\", \"pot2\"], saving_enabled=False)\n","await asyncio.sleep(2)\n","streamer.stop_streaming()"]},{"cell_type":"markdown","metadata":{},"source":["Now let's take a look at the streamed buffers for \"pot2\". Each buffer has the form `{\"ref_timestamp\": 912831, \"data\":[0.23, 0.24, ...], \"rel_timestamps\":[ 0, 12, ...]}`. `ref_timestamp` corresponds, as in the dense case, to the timestamp of the first data point in the `data` array. `rel_timestamps` is an array of timestamps relative to `ref_timestamp`. In this case, since we are assigning a value to `pot2` every 12 frames, the timestamps in `rel_timestamps` are `[0, 12, 24, 36, etc.]`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer.streaming_buffers_queue[\"pot2\"]"]},{"cell_type":"markdown","metadata":{},"source":["You can now calculate the absolute timestamps of each data point by adding the values in `rel_timestamps` to `ref_timestamp`:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["[streamer.streaming_buffers_queue[\"pot2\"][0][\"ref_timestamp\"]]*len(streamer.streaming_buffers_queue[\"pot2\"][0][\"rel_timestamps\"]) + streamer.streaming_buffers_queue[\"pot2\"][0][\"rel_timestamps\"]"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["pot2_data = {\"timestamps\":[], \"data\":[]}\n","\n","for _buffer in streamer.streaming_buffers_queue[\"pot2\"]:\n"," pot2_data[\"timestamps\"].extend([_buffer[\"ref_timestamp\"] + i for i in _buffer[\"rel_timestamps\"]])\n"," pot2_data[\"data\"].extend(_buffer[\"data\"])"]},{"cell_type":"markdown","metadata":{},"source":["Note that the timestamps are spaced by 12, as expected:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["df = pd.DataFrame(pot2_data)\n","df.head()"]},{"cell_type":"markdown","metadata":{},"source":[]}],"metadata":{"kernelspec":{"display_name":"pybela-irbKdG5b","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.10.8"},"orig_nbformat":4},"nbformat":4,"nbformat_minor":2} diff --git a/tutorials/notebooks/3_Logger.ipynb b/tutorials/notebooks/5_Logger.ipynb similarity index 97% rename from tutorials/notebooks/3_Logger.ipynb rename to tutorials/notebooks/5_Logger.ipynb index 3f5abb8..853ad24 100644 --- a/tutorials/notebooks/3_Logger.ipynb +++ b/tutorials/notebooks/5_Logger.ipynb @@ -4,9 +4,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela Tutorial 3: Logger\n", + "# pybela Tutorial 5: Logger\n", "This notebook is a tutorial for the Logger class in the pybela python library. As opposed to the Streamer, the Logger stores variable values directly in binary files in the Bela board. This is more reliable than streaming data with the Streamer with the saving mode enabled, which depends on the websocket connection. The Logger will store the data in Bela even if the websocket connection is lost, and you can retrieve the data later. \n", "\n", + "The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n", + "\n", "As with the previous tutorials, you will need to run the `potentiometers` project in Bela. If you haven't done it yet, copy the project onto Bela:" ] }, @@ -261,7 +263,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.9.16" }, "orig_nbformat": 4 }, diff --git a/tutorials/notebooks/5_Controller.ipynb b/tutorials/notebooks/6_Controller.ipynb similarity index 96% rename from tutorials/notebooks/5_Controller.ipynb rename to tutorials/notebooks/6_Controller.ipynb index d4e9bfc..8ae8270 100644 --- a/tutorials/notebooks/5_Controller.ipynb +++ b/tutorials/notebooks/6_Controller.ipynb @@ -4,11 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela Tutorial 5: Controller\n", + "# pybela Tutorial 6: Controller\n", "This notebook is a tutorial for the Controller class in the pybela python library. The Controller class allows you to control the variables in the Bela program using python. \n", "\n", "The Controller class has some limitations: you can only send one value at a time (no buffers) and you can not control the exact frame at which the values will be updated in the Bela program. Moreover, you can't use it at the same time as the Monitor. However, it is still a useful tool if you want to modify variable values in the Bela program without caring too much about the rate and exact timing of the updates.\n", "\n", + "The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n", + "\n", "As with the previous tutorials, you will need to run the `potentiometers` project in Bela. If you haven't done it yet, copy the project onto Bela:" ] }, diff --git a/tutorials/notebooks/7_Sparse-timestamping.ipynb b/tutorials/notebooks/7_Sparse-timestamping.ipynb new file mode 100644 index 0000000..41c0272 --- /dev/null +++ b/tutorials/notebooks/7_Sparse-timestamping.ipynb @@ -0,0 +1 @@ +{"cells":[{"cell_type":"markdown","metadata":{},"source":["# pybela Tutorial 7: Sparse timestamping\n","In the potentiometer example used in the previous tutorials, the values for `pot1` and `pot2` are assigned at every audio frame. Let's take a look again at the `render()` loop (the Bela code for this example can be found in (in `bela-code/potentiometers/render.cpp`).\n","\n","```cpp\n","void render(BelaContext *context, void *userData)\n","{\n","\tfor(unsigned int n = 0; n < context->audioFrames; n++) {\n","\t\tif(gAudioFramesPerAnalogFrame && !(n % gAudioFramesPerAnalogFrame)) {\n","\t\t\t\n","\t\t\tuint64_t frames = context->audioFramesElapsed/gAudioFramesPerAnalogFrame + n/gAudioFramesPerAnalogFrame;\n","\t\t\tBela_getDefaultWatcherManager()->tick(frames); // watcher timestamps\n","\t\t\t\n","\t\t\tpot1 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot1Ch);\n","\t\t\tpot2 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot2Ch);\n","\t\t\t\n","\t\t}\n","\t}\n","}\n","```\n","\n","\n","The Watched clock is also \"ticked\" at every analog frame, so that the timestamps in the data correspond to the audio frames in the Bela code. The data buffers we received from Bela in the Streamer and the Logger had this form: `{\"ref_timestamp\": 92381, \"data\":[0.34, 0.45, ...]}`. Each data point is registered in the buffer every time we assign a value to `pot1` and `pot2` in the Bela code. The `ref_timestamp` corresponds to the timestamp of the first sample in the `data` array, in this case `0.34`. Since in the Bela code, we assign `pot1` and `pot2` at every audio frame, we can infer the timestamps of each value in the data array by incrementing `ref_timestamp` by 1 for each sample. \n","\n","This is an efficient way of storing data since instead of storing the timestamp of every item in the data array, we only store the timestamp of the first item. We call this *dense* timestamping. However, for many applications, we might not assign a value to a variable every frame, we might do it more than once per frame, once every few frames, or we might want to do it at irregular intervals. In these cases, we need to store the timestamp of every item in the data array. We call this *sparse* timestamping.\n","\n","In this tutorial we take a look at *sparse* timestamping. The complete documentation for the pybela library can be found in [https://belaplatform.github.io/pybela/](https://belaplatform.github.io/pybela/).\n","\n","First, transfer the Bela code we will use in this tutorial to Bela:\n"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["!rsync -rvL ../bela-code/timestamping root@bela.local:Bela/projects"]},{"cell_type":"markdown","metadata":{},"source":["Then you can compile and run the project using either the IDE or by running the following command in the Terminal:\n","```bash\n","ssh root@bela.local \"make -C Bela stop Bela PROJECT=potentiometers run\" \n","```\n","(Running this on a jupyter notebook will block the cell until the program is stopped on Bela.)"]},{"cell_type":"markdown","metadata":{},"source":["As in the previous tutorials, we will use two potentiometers connected to Bela analog inputs 0 and 1. Check the `1_Streamer.ipnyb` tutorial notebook for instructions on how to set up the circuit. \n","\n","### Bela C++ code\n","\n","\n","First, let's take a look at the Bela code. First, we have added `WatcherManager::kTimestampSample` to the declaration of `pot2`. This informs the Bela Watcher that `pot2` will be watched sparsely, that is, that the watcher will store a timestamp for every value assigned to `pot2`:\n","\n","```cpp\n","Watcher pot1(\"pot1\");\n","Watcher pot2(\"pot2\", WatcherManager::kTimestampSample);\n","```\n","\n","Now let's take a look at `render()`:\n","\n","```cpp\n","void render(BelaContext *context, void *userData)\n","{\n","\tfor(unsigned int n = 0; n < context->audioFrames; n++) {\n","\t\tif(gAudioFramesPerAnalogFrame && !(n % gAudioFramesPerAnalogFrame)) {\n","\t\t\t\n","\t\t\tuint64_t frames = context->audioFramesElapsed/gAudioFramesPerAnalogFrame + n/gAudioFramesPerAnalogFrame;\n","\t\t\tBela_getDefaultWatcherManager()->tick(frames); // watcher timestamps\n","\t\t\t\n","\t\t\tpot1 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot1Ch);\n","\n","\t\t\tif (frames % 12==0){\n","\t\t\t\tpot2 = analogRead(context, n/gAudioFramesPerAnalogFrame, gPot2Ch);\n","\t\t\t}\n","\t\t}\n","\t}\n","}\n","```\n","\n","We are \"ticking\" the Bela Watcher once per analog frame, so that the timestamps in the data correspond to the analog frames in the Bela code. We are assigning a value to `pot1` at every analog frame, as in the previous examples, but we are now only assigning a value to `pot2` every 12 frames. \n","\n","### Dealing with sparse timestamps in Python\n","\n","Let's now take a look at the data we receive from Bela. We will use the Streamer. Run the cells below to declare and connect the Streamer to Bela:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["import asyncio\n","import pandas as pd\n","from pybela import Streamer"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer = Streamer()\n","streamer.connect()"]},{"cell_type":"markdown","metadata":{},"source":["We can call `.list()` to take a look at the variables available to be streamed, their types and timestamp mode:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer.list()"]},{"cell_type":"markdown","metadata":{},"source":["`timestampMode` indicates if the timestamping is *sparse* (1) or *dense* (0). Now let's stream the data from Bela. We will stream `pot1` and `pot2`:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer.start_streaming(variables=[\"pot1\", \"pot2\"], saving_enabled=False)\n","await asyncio.sleep(2)\n","streamer.stop_streaming()"]},{"cell_type":"markdown","metadata":{},"source":["Now let's take a look at the streamed buffers for \"pot2\". Each buffer has the form `{\"ref_timestamp\": 912831, \"data\":[0.23, 0.24, ...], \"rel_timestamps\":[ 0, 12, ...]}`. `ref_timestamp` corresponds, as in the dense case, to the timestamp of the first data point in the `data` array. `rel_timestamps` is an array of timestamps relative to `ref_timestamp`. In this case, since we are assigning a value to `pot2` every 12 frames, the timestamps in `rel_timestamps` are `[0, 12, 24, 36, etc.]`."]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["streamer.streaming_buffers_queue[\"pot2\"]"]},{"cell_type":"markdown","metadata":{},"source":["You can now calculate the absolute timestamps of each data point by adding the values in `rel_timestamps` to `ref_timestamp`:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["[streamer.streaming_buffers_queue[\"pot2\"][0][\"ref_timestamp\"]]*len(streamer.streaming_buffers_queue[\"pot2\"][0][\"rel_timestamps\"]) + streamer.streaming_buffers_queue[\"pot2\"][0][\"rel_timestamps\"]"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["pot2_data = {\"timestamps\":[], \"data\":[]}\n","\n","for _buffer in streamer.streaming_buffers_queue[\"pot2\"]:\n"," pot2_data[\"timestamps\"].extend([_buffer[\"ref_timestamp\"] + i for i in _buffer[\"rel_timestamps\"]])\n"," pot2_data[\"data\"].extend(_buffer[\"data\"])"]},{"cell_type":"markdown","metadata":{},"source":["Note that the timestamps are spaced by 12, as expected:"]},{"cell_type":"code","execution_count":null,"metadata":{},"outputs":[],"source":["df = pd.DataFrame(pot2_data)\n","df.head()"]},{"cell_type":"markdown","metadata":{},"source":[]}],"metadata":{"kernelspec":{"display_name":"pybela-irbKdG5b","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.9.16"},"orig_nbformat":4},"nbformat":4,"nbformat_minor":2}