From 28ceb60f0cf1472672fb15304ef4e8c2e94bb2d3 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Mon, 24 Jun 2024 20:49:24 +0100 Subject: [PATCH 01/29] added insertion_counts for the streamer queue, fixed bug with passing single variable as a string to the streamer, now cues for starting and finishing logging --- pybela/Logger.py | 5 +++++ pybela/Streamer.py | 15 ++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/pybela/Logger.py b/pybela/Logger.py index b01ee4e..66c1230 100644 --- a/pybela/Logger.py +++ b/pybela/Logger.py @@ -177,6 +177,9 @@ async def _async_send_logging_cmd_and_wait_for_response(var): remote_files[var] = asyncio.run(_async_send_logging_cmd_and_wait_for_response(var))[ "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 @@ -194,6 +197,8 @@ async def async_stop_logging(variables=[]): self.send_ctrl_msg( {"watcher": [{"cmd": "unlog", "watchers": variables}]}) + + print_info(f"Stopped streaming variables {variables}...") await asyncio.gather(*self._active_copying_tasks, return_exceptions=True) self._active_copying_tasks.clear() diff --git a/pybela/Streamer.py b/pybela/Streamer.py index bd21fe6..ac30589 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -36,6 +36,7 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add # number of streaming buffers (not of data points!) self._streaming_buffers_queue_length = 1000 self._streaming_buffers_queue = None + self._streaming_buffers_queue_insertion_counts = {} 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 @@ -87,6 +88,7 @@ def streaming_buffers_queue_length(self, value): self._streaming_buffers_queue_length = value self._streaming_buffers_queue = {var["name"]: deque( maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} # resize streaming buffer + self._streaming_buffers_queue_insertion_counts = {var["name"]: 0 for var in self.watcher_vars} @property def streaming_buffers_queue(self): @@ -110,7 +112,8 @@ def start(self): 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} - + self._streaming_buffers_queue_insertion_counts = {var["name"]: 0 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. @@ -144,7 +147,7 @@ def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_ saving_filename, saving_dir) if saving_enabled else None # 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="./"): """ @@ -159,7 +162,7 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving saving_filename (str, optional) Filename for saving the streamed data. Defaults to None. """ - self.__streaming_common_routine( + variables = self.__streaming_common_routine( variables, saving_enabled, saving_filename, saving_dir) self._streaming_mode = "FOREVER" if self._peek_response is None else "PEEK" @@ -233,7 +236,7 @@ def schedule_streaming(self, variables=[], timestamps=[], durations=[], saving_e saving_dir (str, optional): Directory for saving the streamed data files. Defaults to "./". """ - self.__streaming_common_routine( + variables = self.__streaming_common_routine( variables, saving_enabled, saving_filename, saving_dir) self._streaming_mode = "SCHEDULE" @@ -311,7 +314,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s """ # resizes the streaming buffer size to n_values and returns it when full - self.__streaming_common_routine( + variables = self.__streaming_common_routine( variables=variables, saving_enabled=saving_enabled, saving_filename=saving_filename, saving_dir=saving_dir) self._streaming_mode = "N_VALUES" # flag cleared in __rec_msg_callback @@ -544,6 +547,8 @@ def _process_data_msg(self, msg): self._streaming_buffers_queue[var_name]) _var_streaming_buffers_queue.append(parsed_buffer) self._streaming_buffers_queue[var_name] = _var_streaming_buffers_queue + _var_streaming_buffers_queue_insertion_counts = copy.deepcopy(self._streaming_buffers_queue_insertion_counts[var_name]) + 1 + self._streaming_buffers_queue_insertion_counts[var_name] = _var_streaming_buffers_queue_insertion_counts # populate last streamed buffer if self._mode == "STREAM": From d968b7f802927a63fb8e68a4db0a09c225906a25 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 27 Jun 2024 18:22:45 +0100 Subject: [PATCH 02/29] added the send_buffer method to send buffers from python to Bela, added test, renamed tutorials and added tutorial draft for .send_buffers --- .gitignore | 3 +- Pipfile | 2 +- Pipfile.lock | 2048 +++++++++-------- pybela/Streamer.py | 48 +- pybela/Watcher.py | 7 +- readme.md | 12 +- test/bela-test-send/Watcher.cpp | 1 + test/bela-test-send/Watcher.h | 1 + test/bela-test-send/render.cpp | 87 + test/test-send.py | 45 + test/test.py | 3 +- ....ipynb => 1_Streamer-Bela-to-python.ipynb} | 8 +- .../notebooks/2_Streamer-python-to-Bela.ipynb | 64 + .../{2_Monitor.ipynb => 3_Monitor.ipynb} | 2 +- .../{3_Logger.ipynb => 4_Logger.ipynb} | 2 +- ...ping.ipynb => 5_Sparse-timestamping.ipynb} | 2 +- ...{5_Controller.ipynb => 6_Controller.ipynb} | 0 17 files changed, 1340 insertions(+), 995 deletions(-) create mode 120000 test/bela-test-send/Watcher.cpp create mode 120000 test/bela-test-send/Watcher.h create mode 100644 test/bela-test-send/render.cpp create mode 100644 test/test-send.py rename tutorials/notebooks/{1_Streamer.ipynb => 1_Streamer-Bela-to-python.ipynb} (97%) create mode 100644 tutorials/notebooks/2_Streamer-python-to-Bela.ipynb rename tutorials/notebooks/{2_Monitor.ipynb => 3_Monitor.ipynb} (99%) rename tutorials/notebooks/{3_Logger.ipynb => 4_Logger.ipynb} (99%) rename tutorials/notebooks/{4_Sparse_timestamping.ipynb => 5_Sparse-timestamping.ipynb} (99%) rename tutorials/notebooks/{5_Controller.ipynb => 6_Controller.ipynb} (100%) diff --git a/.gitignore b/.gitignore index 6bb8e1c..9895b54 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ build/ *.bin *.fzz dist/ -*.sh \ No newline at end of file +*.sh +.clang-format \ No newline at end of file diff --git a/Pipfile b/Pipfile index 0409218..1962322 100644 --- a/Pipfile +++ b/Pipfile @@ -25,4 +25,4 @@ twine = "*" test = "python test/test.py" [requires] -python_version = "3.10" +python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index 91651f6..ad8985e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "02b1160ef6935b4440055ecd931a1de0e774c6eec6ada4057526241b1f6f8515" + "sha256": "b903cef3253d70316d0c519b619506f1a4bda2cb0751ec47fff2c0afe4ebd4e6" }, "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:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", + "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" ], "markers": "python_version >= '3.7'", - "version": "==23.1.0" + "version": "==23.2.0" }, "babel": { "hashes": [ - "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900", - "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed" + "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb", + "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413" ], - "markers": "python_version >= '3.7'", - "version": "==2.13.1" + "markers": "python_version >= '3.8'", + "version": "==2.15.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:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64", + "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf", + "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05", + "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c", + "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15", + "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991", + "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623", + "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834", + "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08", + "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a", + "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74", + "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455", + "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3", + "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73", + "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611", + "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2", + "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d", + "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286", + "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978", + "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d", + "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc", + "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6", + "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed", + "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650", + "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84", + "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1", + "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a" ], - "markers": "python_version >= '3.6'", - "version": "==4.0.1" + "markers": "python_version >= '3.7'", + "version": "==4.1.3" }, "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,20 +293,19 @@ }, "bokeh": { "hashes": [ - "sha256:2a7b3702d7e9f03ef4cd801b02b7380196c70cff2773859bcb84fa565218955c", - "sha256:783fb503d80306fb1e3c06e9c775d98675bf9e07514a776d7109178798e85683" + "sha256:931a43ee59dbf1720383ab904f8205e126b85561aac55592415b800c96f1b0eb", + "sha256:a16d5cc0abb93d2d270d70fc35851f3e1b9208814a985a4678e0ba5ef2d9cd42" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==3.3.1" + "version": "==3.4.2" }, "certifi": { "hashes": [ - "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082", - "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9" + "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", + "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2024.6.2" }, "cffi": { "hashes": [ @@ -358,7 +362,7 @@ "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956", "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357" ], - "markers": "python_version >= '3.8'", + "markers": "platform_python_implementation != 'PyPy'", "version": "==1.16.0" }, "charset-normalizer": { @@ -459,114 +463,127 @@ }, "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:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", + "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", + "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", + "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", + "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", + "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", + "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", + "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", + "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", + "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", + "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", + "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", + "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", + "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", + "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", + "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", + "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", + "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", + "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", + "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", + "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", + "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", + "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", + "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", + "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", + "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", + "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", + "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", + "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", + "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", + "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", + "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" ], "markers": "python_version >= '3.7'", - "version": "==41.0.5" + "version": "==42.0.8" }, "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:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3", + "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9", + "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859", + "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755", + "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca", + "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad", + "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6", + "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02", + "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d", + "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835", + "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3", + "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2", + "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326", + "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a", + "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00", + "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634", + "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e", + "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031", + "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210", + "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa", + "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1", + "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47" ], "markers": "python_version >= '3.8'", - "version": "==1.8.0" + "version": "==1.8.2" }, "decorator": { "hashes": [ @@ -586,11 +603,11 @@ }, "exceptiongroup": { "hashes": [ - "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9", - "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3" + "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", + "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" ], "markers": "python_version < '3.11'", - "version": "==1.1.3" + "version": "==1.2.1" }, "executing": { "hashes": [ @@ -602,10 +619,10 @@ }, "fastjsonschema": { "hashes": [ - "sha256:b9fd1a2dd6971dbc7fee280a95bd199ae0dd9ce22beb91cc75e9c1c528a5170e", - "sha256:e25df6647e1bc4a26070b700897b07b542ec898dd4f1f6ea013e7f6a88417225" + "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23", + "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a" ], - "version": "==2.19.0" + "version": "==2.20.0" }, "fqdn": { "hashes": [ @@ -614,38 +631,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:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f", + "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812" + ], + "markers": "python_version < '3.10'", + "version": "==8.0.0" }, "ipykernel": { "hashes": [ - "sha256:3ba3dc97424b87b31bb46586b5167b3161b32d7820b9201a9e698c71e271602c", - "sha256:553856658eb8430bbe9653ea041a41bff63e9606fc4628873fc92a6cf3abd404" + "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da", + "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c" ], "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==6.26.0" + "version": "==6.29.4" }, "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 +712,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:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7", + "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802" ], "markers": "python_version >= '3.8'", - "version": "==4.19.2" + "version": "==4.22.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 +760,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 +784,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:12558d158ec7a0653bf96cc272bc7ad79e0127d503b982ed144399346694f726", + "sha256:16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5" ], "markers": "python_version >= '3.8'", - "version": "==2.10.0" + "version": "==2.14.1" }, "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:59ee9b839f43308c3dfd55d72d1f1a299ed42a7f91f2d1afe9c12a783f9e525f", + "sha256:a534b6a25719a92a40d514fb133a9fe8f0d9981b0bbce5d8a5fcaa33344a3038" ], "markers": "python_version >= '3.8'", - "version": "==4.0.8" + "version": "==4.2.2" }, "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:15cbb349dc45e954e09bacf81b9f9bcb10815ff660fb2034ecd7417db3a7ea27", + "sha256:54aa2d64fd86383b5438d9f0c032f043c4d8c0264b8af9f60bd061157466ea43" ], "markers": "python_version >= '3.8'", - "version": "==2.25.1" + "version": "==2.27.2" }, "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 +880,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 +978,323 @@ }, "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:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f", + "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238", + "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f", + "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95", + "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a", + "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a", + "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2", + "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2", + "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f", + "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609", + "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f", + "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad", + "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86", + "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65", + "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb", + "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995", + "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a", + "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85", + "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4", + "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275", + "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1", + "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196", + "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d", + "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e", + "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514", + "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f", + "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6", + "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4", + "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44", + "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df", + "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581", + "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787", + "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5", + "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc", + "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871", + "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54", + "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2", + "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98", + "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9", + "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864", + "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de", + "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289", + "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b", + "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c", + "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9" ], "markers": "python_version >= '3.9'", - "version": "==1.26.2" + "version": "==2.0.0" }, "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:659e9fc5b495e6519c5d07e8148fa5eeed9bc648356ec83fc299381ba5a726ef", + "sha256:b49bb9676567b0c0730bf69348c057247080811aec56364dd4fcfba80e5e09a0" ], "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.3.1" + "version": "==1.4.4" }, "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:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c", + "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2", + "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb", + "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d", + "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa", + "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3", + "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1", + "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a", + "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd", + "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8", + "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999", + "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599", + "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936", + "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375", + "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d", + "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b", + "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60", + "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572", + "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3", + "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced", + "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f", + "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b", + "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19", + "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f", + "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d", + "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383", + "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795", + "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355", + "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57", + "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09", + "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b", + "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462", + "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf", + "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f", + "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a", + "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad", + "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9", + "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d", + "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45", + "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994", + "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d", + "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338", + "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463", + "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451", + "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591", + "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c", + "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd", + "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32", + "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9", + "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf", + "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5", + "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828", + "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3", + "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5", + "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2", + "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b", + "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2", + "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475", + "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3", + "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb", + "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef", + "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015", + "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002", + "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170", + "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84", + "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57", + "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f", + "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27", + "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a" + ], + "markers": "python_version >= '3.8'", + "version": "==10.3.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": [ @@ -1244,18 +1317,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 +1349,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,18 +1365,18 @@ }, "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:31541b976a21b7738557c3ea23bd8e44e94e736b9ed269570dcc28db4449d7e3", + "sha256:3167df932656416c4bd711205dad47e986a3ebae1f316258ddc26f9e01513ef7" ], "markers": "python_version >= '3.8'", - "version": "==3.0.0" + "version": "==3.0.2" }, "pyyaml": { "hashes": [ @@ -1335,6 +1409,7 @@ "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4", "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba", "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8", + "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5", "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd", "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3", @@ -1362,110 +1437,105 @@ }, "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:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa", + "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4", + "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09", + "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753", + "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c", + "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537", + "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5", + "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620", + "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920", + "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77", + "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450", + "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a", + "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc", + "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0", + "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de", + "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18", + "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606", + "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500", + "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972", + "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6", + "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad", + "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee", + "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a", + "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59", + "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7", + "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709", + "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625", + "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d", + "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527", + "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5", + "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987", + "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d", + "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b", + "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de", + "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12", + "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798", + "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be", + "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2", + "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20", + "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67", + "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4", + "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd", + "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a", + "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1", + "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4", + "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381", + "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd", + "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02", + "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35", + "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7", + "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce", + "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5", + "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf", + "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf", + "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84", + "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c", + "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5", + "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32", + "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a", + "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90", + "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97", + "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8", + "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5", + "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94", + "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf", + "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f", + "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2", + "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17", + "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879", + "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81", + "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223", + "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a", + "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b", + "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab", + "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7", + "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6", + "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2", + "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480", + "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8", + "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67", + "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad", + "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b", + "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3", + "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9", + "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47", + "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83", + "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad", + "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc" ], - "markers": "python_version >= '3.6'", - "version": "==25.1.1" + "markers": "python_version >= '3.7'", + "version": "==26.0.3" }, "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 +1547,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 +1579,124 @@ }, "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:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee", + "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc", + "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc", + "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944", + "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20", + "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7", + "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4", + "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6", + "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6", + "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93", + "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633", + "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0", + "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360", + "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8", + "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139", + "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7", + "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a", + "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9", + "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26", + "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724", + "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72", + "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b", + "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09", + "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100", + "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3", + "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261", + "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3", + "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9", + "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b", + "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3", + "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de", + "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d", + "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e", + "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8", + "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff", + "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5", + "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c", + "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e", + "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e", + "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4", + "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8", + "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922", + "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338", + "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d", + "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8", + "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2", + "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72", + "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80", + "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644", + "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae", + "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163", + "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104", + "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d", + "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60", + "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a", + "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d", + "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07", + "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49", + "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10", + "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f", + "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2", + "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8", + "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7", + "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88", + "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65", + "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0", + "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909", + "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8", + "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c", + "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184", + "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397", + "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a", + "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346", + "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590", + "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333", + "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb", + "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74", + "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e", + "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d", + "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa", + "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f", + "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53", + "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1", + "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac", + "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0", + "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd", + "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611", + "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f", + "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c", + "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5", + "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab", + "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc", + "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43", + "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da", + "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac", + "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843", + "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e", + "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89", + "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64" + ], + "markers": "python_version >= '3.8'", + "version": "==0.18.1" }, "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:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650", + "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95" + ], + "markers": "python_version >= '3.8'", + "version": "==70.1.1" }, "six": { "hashes": [ @@ -1630,11 +1708,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 +1731,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 +1755,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:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644", + "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb" ], "markers": "python_version >= '3.7'", - "version": "==4.66.1" + "version": "==4.66.4" }, "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 +1827,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 +1856,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 +1938,57 @@ "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": { + "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:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", + "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" ], "markers": "python_version >= '3.6'", - "version": "==2023.7.22" + "version": "==2024.6.2" }, "charset-normalizer": { "hashes": [ @@ -1995,43 +2088,59 @@ }, "docutils": { "hashes": [ - "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", - "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" + "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", + "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2" ], - "markers": "python_version >= '3.7'", - "version": "==0.20.1" + "markers": "python_version >= '3.9'", + "version": "==0.21.2" }, "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:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb", - "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743" + "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f", + "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812" ], - "markers": "python_version >= '3.8'", - "version": "==6.8.0" + "markers": "python_version < '3.10'", + "version": "==8.0.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:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664", + "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8" + ], + "markers": "python_version >= '3.8'", + "version": "==4.0.1" }, "keyring": { "hashes": [ - "sha256:4446d35d636e6a10b8bce7caa66913dd9eca5fd222ca03a3d42c38608ac30836", - "sha256:e730ecffd309658a08ee82535a3b5ec4b4c8669a9be11efb66249d8e0aeb9a25" + "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50", + "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b" ], "markers": "python_version >= '3.8'", - "version": "==24.3.0" + "version": "==25.2.1" }, "markdown-it-py": { "hashes": [ @@ -2051,80 +2160,80 @@ }, "more-itertools": { "hashes": [ - "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a", - "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6" + "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463", + "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320" ], "markers": "python_version >= '3.8'", - "version": "==10.1.0" + "version": "==10.3.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:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a", + "sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911", + "sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb", + "sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a", + "sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc", + "sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028", + "sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9", + "sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3", + "sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351", + "sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10", + "sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71", + "sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f", + "sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b", + "sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a", + "sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062", + "sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a" + ], + "version": "==0.2.17" }, "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" }, "pkginfo": { "hashes": [ - "sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546", - "sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046" + "sha256:2e0dca1cf4c8e39644eed32408ea9966ee15e0d324c62ba899a393b3c6b467aa", + "sha256:bfa76a714fdfc18a045fcd684dbfc3816b603d9d075febef17cb6582bea29573" ], - "markers": "python_version >= '3.6'", - "version": "==1.9.6" + "markers": "python_version >= '3.8'", + "version": "==1.11.1" }, "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 +2253,11 @@ }, "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" }, "tomli": { "hashes": [ @@ -2160,28 +2269,27 @@ }, "twine": { "hashes": [ - "sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8", - "sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8" + "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4", + "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==4.0.2" + "version": "==5.0.0" }, "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/pybela/Streamer.py b/pybela/Streamer.py index ac30589..cfc8e0c 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -8,6 +8,7 @@ from itertools import cycle import warnings import re +import struct import bokeh.plotting import bokeh.io @@ -88,7 +89,8 @@ def streaming_buffers_queue_length(self, value): self._streaming_buffers_queue_length = value self._streaming_buffers_queue = {var["name"]: deque( maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} # resize streaming buffer - self._streaming_buffers_queue_insertion_counts = {var["name"]: 0 for var in self.watcher_vars} + self._streaming_buffers_queue_insertion_counts = { + var["name"]: 0 for var in self.watcher_vars} @property def streaming_buffers_queue(self): @@ -112,8 +114,9 @@ def start(self): 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} - self._streaming_buffers_queue_insertion_counts = {var["name"]: 0 for var in self.watcher_vars} - + self._streaming_buffers_queue_insertion_counts = { + var["name"]: 0 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. @@ -165,6 +168,16 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving variables = self.__streaming_common_routine( variables, saving_enabled, saving_filename, saving_dir) + # 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": if periods != []: @@ -172,12 +185,14 @@ 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}]}) + # 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( f"Started monitoring variables {variables}... Run stop_monitoring() to stop monitoring.") @@ -363,6 +378,27 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s return self.streaming_buffers_queue + def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list): + """ + 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(' +#include +#include + +Watcher myvar1("myvar1"); +Watcher myvar2("myvar2"); + +struct ReceivedBuffer { + uint32_t bufferId; + char bufferType[4]; + uint32_t bufferLen; + uint32_t empty; + std::vector bufferData; +}; + +ReceivedBuffer receivedBuffer; +uint receivedBufferHeaderSize; + +struct Buffer { + uint32_t guiBufferId; + std::vector bufferData; + uint64_t count; +}; + +Buffer buffer_1; +Buffer buffer_2; +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); + + if (receivedBuffer.bufferId == 0) { + buffer_1.bufferData = receivedBuffer.bufferData; + buffer_1.count++; + for (size_t i = 0; i < buffer_1.bufferData.size(); ++i) { + Bela_getDefaultWatcherManager()->tick(totalReceivedCount); + myvar1 = buffer_1.bufferData[i]; + } + + } else if (receivedBuffer.bufferId == 1) { + buffer_2.bufferData = receivedBuffer.bufferData; + buffer_2.count++; + for (size_t i = 0; i < buffer_1.bufferData.size(); ++i) { + Bela_getDefaultWatcherManager()->tick(totalReceivedCount); + myvar2 = buffer_2.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 + + buffer_1.guiBufferId = Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); // for myvar + buffer_1.count = 0; + buffer_2.guiBufferId = Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); // for myvar2 + buffer_2.count = 0; + printf("dataBufferId_1: %d, dataBufferId_2: %d \n", buffer_1.guiBufferId, buffer_2.guiBufferId); + + 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/test-send.py b/test/test-send.py new file mode 100644 index 0000000..e1229d2 --- /dev/null +++ b/test/test-send.py @@ -0,0 +1,45 @@ +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" + + +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..ece7ba0 100644 --- a/test/test.py +++ b/test/test.py @@ -410,8 +410,7 @@ def remove_file(file_path): if __name__ == '__main__': - if 0: - unittest.main(verbosity=2) + unittest.main(verbosity=2) # select which tests to run n = 1 diff --git a/tutorials/notebooks/1_Streamer.ipynb b/tutorials/notebooks/1_Streamer-Bela-to-python.ipynb similarity index 97% rename from tutorials/notebooks/1_Streamer.ipynb rename to tutorials/notebooks/1_Streamer-Bela-to-python.ipynb index 99da975..6f24776 100644 --- a/tutorials/notebooks/1_Streamer.ipynb +++ b/tutorials/notebooks/1_Streamer-Bela-to-python.ipynb @@ -4,8 +4,10 @@ "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\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. \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", "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:" ] @@ -389,7 +391,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-python-to-Bela.ipynb b/tutorials/notebooks/2_Streamer-python-to-Bela.ipynb new file mode 100644 index 0000000..ff63df0 --- /dev/null +++ b/tutorials/notebooks/2_Streamer-python-to-Bela.ipynb @@ -0,0 +1,64 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# pybela Tutorial 2: 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. \n", + "\n", + "In this tutorial we will be looking at sending data from python to Bela. In this case, the routine is quite simple as the only available functionality is sending a buffer of a certain type and size (the `streamer.send_buffer()` method).\n", + "\n", + "To run this tutorial, first 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.)" + ] + }, + { + "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/3_Monitor.ipynb similarity index 99% rename from tutorials/notebooks/2_Monitor.ipynb rename to tutorials/notebooks/3_Monitor.ipynb index c70b139..d32b366 100644 --- a/tutorials/notebooks/2_Monitor.ipynb +++ b/tutorials/notebooks/3_Monitor.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela tutorial 2: Monitor\n", + "# pybela tutorial 3: 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:" ] }, diff --git a/tutorials/notebooks/3_Logger.ipynb b/tutorials/notebooks/4_Logger.ipynb similarity index 99% rename from tutorials/notebooks/3_Logger.ipynb rename to tutorials/notebooks/4_Logger.ipynb index 3f5abb8..9cb55a0 100644 --- a/tutorials/notebooks/3_Logger.ipynb +++ b/tutorials/notebooks/4_Logger.ipynb @@ -261,7 +261,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/4_Sparse_timestamping.ipynb b/tutorials/notebooks/5_Sparse-timestamping.ipynb similarity index 99% rename from tutorials/notebooks/4_Sparse_timestamping.ipynb rename to tutorials/notebooks/5_Sparse-timestamping.ipynb index 48b0b15..5f4b98a 100644 --- a/tutorials/notebooks/4_Sparse_timestamping.ipynb +++ b/tutorials/notebooks/5_Sparse-timestamping.ipynb @@ -1 +1 @@ -{"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} +{"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.9.16"},"orig_nbformat":4},"nbformat":4,"nbformat_minor":2} diff --git a/tutorials/notebooks/5_Controller.ipynb b/tutorials/notebooks/6_Controller.ipynb similarity index 100% rename from tutorials/notebooks/5_Controller.ipynb rename to tutorials/notebooks/6_Controller.ipynb From b5a88c658c0439cfa28a0a86b353b8e25761a32c Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Fri, 28 Jun 2024 17:24:56 +0100 Subject: [PATCH 03/29] added on_data_callback to the streamer --- Pipfile | 1 + pybela/Streamer.py | 86 +++++++++++++++++++++++++++++++++++++++ test/bela-test/render.cpp | 18 ++------ test/test.py | 30 +++++++++++++- 4 files changed, 120 insertions(+), 15 deletions(-) diff --git a/Pipfile b/Pipfile index 1962322..3c7da0f 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,7 @@ twine = "*" [scripts] test = "python test/test.py" +test-send = "python test/test-send.py" [requires] python_version = "3.9" diff --git a/pybela/Streamer.py b/pybela/Streamer.py index cfc8e0c..8e88de9 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -399,6 +399,92 @@ def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list): print_info( f"Sent buffer {buffer_id} of type {buffer_type} with length {buffer_length}...") + def on_data_callback(self, variables, callback, stop_after=0, *args, **kwargs): + """ Run a callback on each buffer received. + + Args: + variables (list): List of variables to run the callback on. Important: the current implementation assumes that variables are of the same type and that buffers are of the same length (i.e. also same timestamping mode). Check the buffer length of each variable with get_prop_of_var(var, "data_length"). + callback (function): Function to be run on each buffer. It takes the buffer and the variable name as arguments. Callback can be blocking or non-blocking. Example: + def callback(buffer, var): + print(var, buffer["ref_timestamp"]) + print(buffer["data"].shape) + stop_after (int, optional): Stop after processing "stop_after" buffers. If 0, will run forever. Defaults to 0. + """ + data_len = {} + for var in variables: + data_len[var] = self.get_prop_of_var(var, "data_length") + + print(data_len) + + # very experimental + async def async_on_data_callback(variables, callback, stop_after=stop_after, *args, **kwargs): + + _loop = 0 + _in_count = {} # insertion count + _p_count = {} # processed count + _p_idx = {} # processed index + _old_in_count = {} # old insertion count + + for var in variables: + _in_count[var] = self._streaming_buffers_queue_insertion_counts[var] + _p_count[var] = _in_count[var] + + if _in_count[var] >= self.streaming_buffers_queue_length: + _p_idx[var] = self.streaming_buffers_queue_length - 2 + else: + _p_idx[var] = -1 + _p_count[var] + _old_in_count[var] = _in_count[var] + while True: + # insertion count and streaming buffer should be copied at the same time + await asyncio.sleep(0.01) + + _old_in_count = _in_count + _in_count = copy.deepcopy( + self._streaming_buffers_queue_insertion_counts) + _queue = copy.deepcopy(self._streaming_buffers_queue) + + for var in variables: + + _d_in_count = _in_count[var] - _old_in_count[var] + + # if all buffers have been processed + if _in_count[var] == _p_count[var]: + continue + else: + if _in_count[var] <= self.streaming_buffers_queue_length: + _p_idx[var] += 1 + else: + _p_idx[var] += 1 - _d_in_count + + if _p_idx[var] < 0: + _p_idx[var] = 0 + + _buffer = _queue[var][_p_idx[var]] + + # print(var, "in_count", _in_count[var], "_d_in_count", + # _d_in_count, "p_count", _p_count[var], "_p_idx", _p_idx[var]) + + # print(_p_idx[var], _buffer["ref_timestamp"], + # _buffer["ref_timestamp"] // data_len[var]) + + # callback here to process data + if asyncio.iscoroutinefunction(callback): + await callback(_buffer, var, *args, **kwargs) + else: + callback(_buffer, var, *args, **kwargs) + # end callback + + _p_count[var] += 1 + + if self._mode == "OFF": + return # stop if streaming has been stopped + + if stop_after > 0 and all(count >= stop_after for count in _p_count.values()): + return + + asyncio.run(async_on_data_callback( + variables, callback, stop_after=stop_after)) + # - utils def is_streaming(self): 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/test.py b/test/test.py index ece7ba0..245bba3 100644 --- a/test/test.py +++ b/test/test.py @@ -137,6 +137,32 @@ async def async_test_scheduling_streaming(): asyncio.run(async_test_scheduling_streaming()) self.__test_buffers(mode="schedule") + def test_on_data_callback(self): # TODO test with more than one var + variables = ["myvar", "myvar5"] # dense double + + async def async_test_on_data_callback(): + + # test only on vars of the same type + self.streamer.start_streaming(variables, saving_enabled=False) + + timestamps = {var: [] for var in variables} + + def callback(buffer, var): + timestamps[var].append( + int(buffer["ref_timestamp"]/len(buffer["data"]))) + + self.streamer.on_data_callback(variables, callback, stop_after=10) + + self.streamer.stop_streaming(variables) + + for var in variables: + self.assertEqual(len(timestamps[var]), 10, + "The callback should be called 10 times") + self.assertEqual(timestamps[var][-1] - timestamps[var][0], len(timestamps[var]) - 1, + "Timestamps are not continuous. The callback is missing some buffer") + + asyncio.run(async_test_on_data_callback()) + class test_Logger(unittest.TestCase): @@ -410,7 +436,9 @@ def remove_file(file_path): if __name__ == '__main__': - unittest.main(verbosity=2) + # run all tests + if 0: + unittest.main(verbosity=2) # select which tests to run n = 1 From 411cb0a8c4693bfb697fe91fcdd005dc966ccd56 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Fri, 28 Jun 2024 17:48:16 +0100 Subject: [PATCH 04/29] on_data_callback: type and timestamp checks + fix stop_after --- pybela/Streamer.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pybela/Streamer.py b/pybela/Streamer.py index 8e88de9..d7f466d 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -410,21 +410,21 @@ def callback(buffer, var): print(buffer["data"].shape) stop_after (int, optional): Stop after processing "stop_after" buffers. If 0, will run forever. Defaults to 0. """ - data_len = {} + data_len, data_type, timestamp_mode = [], [], [] for var in variables: - data_len[var] = self.get_prop_of_var(var, "data_length") + data_len.append(self.get_prop_of_var(var, "data_length")) + data_type.append(self.get_prop_of_var(var, "type")) + timestamp_mode.append(self.get_prop_of_var(var, "timestamp_mode")) + assert len(set(data_len) + ) == 1, "Variables have different buffer sizes" + assert len(set(data_type)) == 1, "Variables have different data types" + assert len(set(timestamp_mode) + ) == 1, "Variables have different timestamp modes" - print(data_len) - - # very experimental async def async_on_data_callback(variables, callback, stop_after=stop_after, *args, **kwargs): - _loop = 0 - _in_count = {} # insertion count - _p_count = {} # processed count - _p_idx = {} # processed index - _old_in_count = {} # old insertion count - + _in_count, _p_count, _rp_count, _p_idx, _old_in_count = {}, {}, {}, {}, {} # insertion count, processed count, real processed count (without init bias), processed index, old insertion count + _rp_count = {var: 0 for var in variables} for var in variables: _in_count[var] = self._streaming_buffers_queue_insertion_counts[var] _p_count[var] = _in_count[var] @@ -434,6 +434,7 @@ async def async_on_data_callback(variables, callback, stop_after=stop_after, *ar else: _p_idx[var] = -1 + _p_count[var] _old_in_count[var] = _in_count[var] + while True: # insertion count and streaming buffer should be copied at the same time await asyncio.sleep(0.01) @@ -460,6 +461,7 @@ async def async_on_data_callback(variables, callback, stop_after=stop_after, *ar _p_idx[var] = 0 _buffer = _queue[var][_p_idx[var]] + _rp_count[var] += 1 # print(var, "in_count", _in_count[var], "_d_in_count", # _d_in_count, "p_count", _p_count[var], "_p_idx", _p_idx[var]) @@ -479,7 +481,8 @@ async def async_on_data_callback(variables, callback, stop_after=stop_after, *ar if self._mode == "OFF": return # stop if streaming has been stopped - if stop_after > 0 and all(count >= stop_after for count in _p_count.values()): + if stop_after > 0 and all(count >= stop_after for count in _rp_count.values()): + print_info(f"on_data_callback stopped after processing {stop_after} buffers") return asyncio.run(async_on_data_callback( From bc7b26bd8dda80c55edb8c1db717fe9de2004f75 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Mon, 8 Jul 2024 14:34:08 +0200 Subject: [PATCH 05/29] added on_block_callback, tests, added a finish copying functionality if ssh connection breaks during logging --- Pipfile | 2 + Pipfile.lock | 111 ++++++++++----------- pybela/Logger.py | 175 +++++++++++++++++++++------------ pybela/Streamer.py | 83 ++++++++++++---- test/bela-test-send/render.cpp | 43 ++++---- test/test-send.py | 2 +- test/test.py | 113 +++++++++++++-------- 7 files changed, 325 insertions(+), 204 deletions(-) diff --git a/Pipfile b/Pipfile index 3c7da0f..10bb698 100644 --- a/Pipfile +++ b/Pipfile @@ -16,10 +16,12 @@ nest-asyncio = "*" aiofiles = "*" pybela = {editable = true, path = "."} paramiko = "*" +numpy = "==1.26" [dev-packages] build = "*" twine = "*" +pip-chill = "*" [scripts] test = "python test/test.py" diff --git a/Pipfile.lock b/Pipfile.lock index ad8985e..0dff0eb 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b903cef3253d70316d0c519b619506f1a4bda2cb0751ec47fff2c0afe4ebd4e6" + "sha256": "4cd98bfbe97e6b9b8fedd7513069b48c2fce0c509a14a7d036c16822a2242bc9" }, "pipfile-spec": 6, "requires": { @@ -824,11 +824,11 @@ }, "jupyterlab": { "hashes": [ - "sha256:59ee9b839f43308c3dfd55d72d1f1a299ed42a7f91f2d1afe9c12a783f9e525f", - "sha256:a534b6a25719a92a40d514fb133a9fe8f0d9981b0bbce5d8a5fcaa33344a3038" + "sha256:0b59d11808e84bb84105c73364edfa867dd475492429ab34ea388a52f2e2e596", + "sha256:df6e46969ea51d66815167f23d92f105423b7f1f06fa604d4f44aeb018c82c7b" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.2.3" }, "jupyterlab-pygments": { "hashes": [ @@ -1026,54 +1026,41 @@ }, "numpy": { "hashes": [ - "sha256:04494f6ec467ccb5369d1808570ae55f6ed9b5809d7f035059000a37b8d7e86f", - "sha256:0a43f0974d501842866cc83471bdb0116ba0dffdbaac33ec05e6afed5b615238", - "sha256:0e50842b2295ba8414c8c1d9d957083d5dfe9e16828b37de883f51fc53c4016f", - "sha256:0ec84b9ba0654f3b962802edc91424331f423dcf5d5f926676e0150789cb3d95", - "sha256:17067d097ed036636fa79f6a869ac26df7db1ba22039d962422506640314933a", - "sha256:1cde1753efe513705a0c6d28f5884e22bdc30438bf0085c5c486cdaff40cd67a", - "sha256:1e72728e7501a450288fc8e1f9ebc73d90cfd4671ebbd631f3e7857c39bd16f2", - "sha256:2635dbd200c2d6faf2ef9a0d04f0ecc6b13b3cad54f7c67c61155138835515d2", - "sha256:2ce46fd0b8a0c947ae047d222f7136fc4d55538741373107574271bc00e20e8f", - "sha256:34f003cb88b1ba38cb9a9a4a3161c1604973d7f9d5552c38bc2f04f829536609", - "sha256:354f373279768fa5a584bac997de6a6c9bc535c482592d7a813bb0c09be6c76f", - "sha256:38ecb5b0582cd125f67a629072fed6f83562d9dd04d7e03256c9829bdec027ad", - "sha256:3e8e01233d57639b2e30966c63d36fcea099d17c53bf424d77f088b0f4babd86", - "sha256:3f6bed7f840d44c08ebdb73b1825282b801799e325bcbdfa6bc5c370e5aecc65", - "sha256:4554eb96f0fd263041baf16cf0881b3f5dafae7a59b1049acb9540c4d57bc8cb", - "sha256:46e161722e0f619749d1cd892167039015b2c2817296104487cd03ed4a955995", - "sha256:49d9f7d256fbc804391a7f72d4a617302b1afac1112fac19b6c6cec63fe7fe8a", - "sha256:4d2f62e55a4cd9c58c1d9a1c9edaedcd857a73cb6fda875bf79093f9d9086f85", - "sha256:5f64641b42b2429f56ee08b4f427a4d2daf916ec59686061de751a55aafa22e4", - "sha256:63b92c512d9dbcc37f9d81b123dec99fdb318ba38c8059afc78086fe73820275", - "sha256:6d7696c615765091cc5093f76fd1fa069870304beaccfd58b5dcc69e55ef49c1", - "sha256:79e843d186c8fb1b102bef3e2bc35ef81160ffef3194646a7fdd6a73c6b97196", - "sha256:821eedb7165ead9eebdb569986968b541f9908979c2da8a4967ecac4439bae3d", - "sha256:84554fc53daa8f6abf8e8a66e076aff6ece62de68523d9f665f32d2fc50fd66e", - "sha256:8d83bb187fb647643bd56e1ae43f273c7f4dbcdf94550d7938cfc32566756514", - "sha256:903703372d46bce88b6920a0cd86c3ad82dae2dbef157b5fc01b70ea1cfc430f", - "sha256:9416a5c2e92ace094e9f0082c5fd473502c91651fb896bc17690d6fc475128d6", - "sha256:9a1712c015831da583b21c5bfe15e8684137097969c6d22e8316ba66b5baabe4", - "sha256:9c27f0946a3536403efb0e1c28def1ae6730a72cd0d5878db38824855e3afc44", - "sha256:a356364941fb0593bb899a1076b92dfa2029f6f5b8ba88a14fd0984aaf76d0df", - "sha256:a7039a136017eaa92c1848152827e1424701532ca8e8967fe480fe1569dae581", - "sha256:acd3a644e4807e73b4e1867b769fbf1ce8c5d80e7caaef0d90dcdc640dfc9787", - "sha256:ad0c86f3455fbd0de6c31a3056eb822fc939f81b1618f10ff3406971893b62a5", - "sha256:b4c76e3d4c56f145d41b7b6751255feefae92edbc9a61e1758a98204200f30fc", - "sha256:b6f6a8f45d0313db07d6d1d37bd0b112f887e1369758a5419c0370ba915b3871", - "sha256:c5a59996dc61835133b56a32ebe4ef3740ea5bc19b3983ac60cc32be5a665d54", - "sha256:c73aafd1afca80afecb22718f8700b40ac7cab927b8abab3c3e337d70e10e5a2", - "sha256:cee6cc0584f71adefe2c908856ccc98702baf95ff80092e4ca46061538a2ba98", - "sha256:cef04d068f5fb0518a77857953193b6bb94809a806bd0a14983a8f12ada060c9", - "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864", - "sha256:e61155fae27570692ad1d327e81c6cf27d535a5d7ef97648a17d922224b216de", - "sha256:e7f387600d424f91576af20518334df3d97bc76a300a755f9a8d6e4f5cadd289", - "sha256:ed08d2703b5972ec736451b818c2eb9da80d66c3e84aed1deeb0c345fefe461b", - "sha256:fbd6acc766814ea6443628f4e6751d0da6593dae29c08c0b2606164db026970c", - "sha256:feff59f27338135776f6d4e2ec7aeeac5d5f7a08a83e80869121ef8164b74af9" + "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": "==2.0.0" + "index": "pypi", + "version": "==1.26" }, "overrides": { "hashes": [ @@ -2195,13 +2182,21 @@ "markers": "python_version >= '3.8'", "version": "==24.1" }, + "pip-chill": { + "hashes": [ + "sha256:42c3b888efde0b3dc5d5307b92fae5fb67695dd9c29c9d31891b9505dd8b735a", + "sha256:452a38edbcdfc333301c438c26ba00a0762d2034fe26a235d8587134453ccdb1" + ], + "index": "pypi", + "version": "==1.0.3" + }, "pkginfo": { "hashes": [ - "sha256:2e0dca1cf4c8e39644eed32408ea9966ee15e0d324c62ba899a393b3c6b467aa", - "sha256:bfa76a714fdfc18a045fcd684dbfc3816b603d9d075febef17cb6582bea29573" + "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", + "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097" ], - "markers": "python_version >= '3.8'", - "version": "==1.11.1" + "markers": "python_version >= '3.6'", + "version": "==1.10.0" }, "pygments": { "hashes": [ @@ -2269,11 +2264,11 @@ }, "twine": { "hashes": [ - "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4", - "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0" + "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997", + "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db" ], "index": "pypi", - "version": "==5.0.0" + "version": "==5.1.1" }, "urllib3": { "hashes": [ diff --git a/pybela/Logger.py b/pybela/Logger.py index 66c1230..e334912 100644 --- a/pybela/Logger.py +++ b/pybela/Logger.py @@ -54,7 +54,7 @@ def start_logging(self, variables=[], transfer=True, logging_dir="./"): # 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( + copying_task = self.__copy_file_in_chunks( remote_paths[var], local_paths[var]) self._active_copying_tasks.append(copying_task) @@ -114,7 +114,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) @@ -177,7 +177,7 @@ async def _async_send_logging_cmd_and_wait_for_response(var): remote_files[var] = asyncio.run(_async_send_logging_cmd_and_wait_for_response(var))[ "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.") @@ -197,8 +197,8 @@ async def async_stop_logging(variables=[]): self.send_ctrl_msg( {"watcher": [{"cmd": "unlog", "watchers": variables}]}) - - print_info(f"Stopped streaming variables {variables}...") + + print_info(f"Stopped logging variables {variables}...") await asyncio.gather(*self._active_copying_tasks, return_exceptions=True) self._active_copying_tasks.clear() @@ -249,65 +249,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. @@ -378,6 +319,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. @@ -443,6 +443,53 @@ def callback(transferred, to_transfer): return transferred_event.set( 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): diff --git a/pybela/Streamer.py b/pybela/Streamer.py index d7f466d..3dd6a5e 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -404,12 +404,33 @@ def on_data_callback(self, variables, callback, stop_after=0, *args, **kwargs): Args: variables (list): List of variables to run the callback on. Important: the current implementation assumes that variables are of the same type and that buffers are of the same length (i.e. also same timestamping mode). Check the buffer length of each variable with get_prop_of_var(var, "data_length"). - callback (function): Function to be run on each buffer. It takes the buffer and the variable name as arguments. Callback can be blocking or non-blocking. Example: + callback (function): Function to be run on each buffer. It takes the buffer and the variable name as arguments. Callback can be a coroutine (async def ...). Example: def callback(buffer, var): + (buffer is a dict and var is a string) print(var, buffer["ref_timestamp"]) print(buffer["data"].shape) stop_after (int, optional): Stop after processing "stop_after" buffers. If 0, will run forever. Defaults to 0. """ + self.__on_data_callback( + variables, callback, call_callback_per_variable=True, stop_after=stop_after, *args, **kwargs) + + def on_block_callback(self, variables, callback, stop_after=0, *args, **kwargs): + """ Run a callback on each block of buffers received. + + Args: + variables (list): List of variables to run the callback on. Important: the current implementation assumes that variables are of the same type and that buffers are of the same length (i.e. also same timestamping mode). Check the buffer length of each variable with get_prop_of_var(var, "data_length"). + callback (function): Function to be run on each block of buffers. It takes the block and the variables as arguments. Callback can be a coroutine (async def ...). Example: + def callback(block, variables): + (block is a dict and variables is a list of strings) + print(variables) + print(block.keys()) + stop_after (int, optional): Stop after processing "stop_after" buffers. If 0, will run forever. Defaults to 0. + """ + self.__on_data_callback( + variables, callback, call_callback_per_variable=False, stop_after=stop_after, *args, **kwargs) + + def __on_data_callback(self, variables, callback, call_callback_per_variable=True, stop_after=0, *args, **kwargs): + data_len, data_type, timestamp_mode = [], [], [] for var in variables: data_len.append(self.get_prop_of_var(var, "data_length")) @@ -421,10 +442,15 @@ def callback(buffer, var): assert len(set(timestamp_mode) ) == 1, "Variables have different timestamp modes" - async def async_on_data_callback(variables, callback, stop_after=stop_after, *args, **kwargs): + data_len = {var: self.get_prop_of_var( + var, "data_length") for var in variables} + + async def async_on_data_callback(variables, callback, call_callback_per_variable=True, stop_after=stop_after, *args, **kwargs): - _in_count, _p_count, _rp_count, _p_idx, _old_in_count = {}, {}, {}, {}, {} # insertion count, processed count, real processed count (without init bias), processed index, old insertion count + # insertion count, processed count, real processed count (without init bias), processed index, old insertion count, insertion count delta + _in_count, _p_count, _rp_count, _p_idx, _old_in_count, _d_in_count = {}, {}, {}, {}, {}, {} _rp_count = {var: 0 for var in variables} + _d_in_count = {var: 0 for var in variables} for var in variables: _in_count[var] = self._streaming_buffers_queue_insertion_counts[var] _p_count[var] = _in_count[var] @@ -434,19 +460,22 @@ async def async_on_data_callback(variables, callback, stop_after=stop_after, *ar else: _p_idx[var] = -1 + _p_count[var] _old_in_count[var] = _in_count[var] - + while True: # insertion count and streaming buffer should be copied at the same time - await asyncio.sleep(0.01) + await asyncio.sleep(0.00001) _old_in_count = _in_count - _in_count = copy.deepcopy( + # _in_count = {var: value for (var, value) in self._streaming_buffers_queue_insertion_counts.items()} + # _queue = {var: value for (var, value) in self._streaming_buffers_queue.items()} + _in_count = copy.copy( self._streaming_buffers_queue_insertion_counts) - _queue = copy.deepcopy(self._streaming_buffers_queue) + _queue = copy.copy(self._streaming_buffers_queue) + _block = {var: {} for var in variables} for var in variables: - _d_in_count = _in_count[var] - _old_in_count[var] + _d_in_count[var] = _in_count[var] - _old_in_count[var] # if all buffers have been processed if _in_count[var] == _p_count[var]: @@ -461,32 +490,44 @@ async def async_on_data_callback(variables, callback, stop_after=stop_after, *ar _p_idx[var] = 0 _buffer = _queue[var][_p_idx[var]] + _block[var] = _buffer _rp_count[var] += 1 + # debug # print(var, "in_count", _in_count[var], "_d_in_count", # _d_in_count, "p_count", _p_count[var], "_p_idx", _p_idx[var]) # print(_p_idx[var], _buffer["ref_timestamp"], # _buffer["ref_timestamp"] // data_len[var]) - # callback here to process data + _p_count[var] += 1 + + if call_callback_per_variable: + if asyncio.iscoroutinefunction(callback): + # non-blocking + asyncio.run( + callback(_buffer, var, *args, **kwargs)) + else: + callback(_buffer, var, *args, **kwargs) + + # if call_per_block is enabled, if there are processed buffers and if there are new buffers + if not call_callback_per_variable and any(_rp_count.values()) and any(_d_in_count.values()): if asyncio.iscoroutinefunction(callback): - await callback(_buffer, var, *args, **kwargs) + asyncio.run(callback(_block, variables, * + args, **kwargs)) # non-blocking else: - callback(_buffer, var, *args, **kwargs) - # end callback + callback(_block, variables, *args, **kwargs) - _p_count[var] += 1 - - if self._mode == "OFF": + if not self.is_streaming(): return # stop if streaming has been stopped if stop_after > 0 and all(count >= stop_after for count in _rp_count.values()): - print_info(f"on_data_callback stopped after processing {stop_after} buffers") + print_info( + f"on_data_callback stopped after processing {stop_after} buffers") return asyncio.run(async_on_data_callback( - variables, callback, stop_after=stop_after)) + variables, callback, call_callback_per_variable, stop_after=stop_after)) # - utils @@ -498,6 +539,14 @@ def is_streaming(self): """ return True if self._streaming_mode != "OFF" else False + def flush_queue(self): + """Flushes the streaming buffers queue. The queue is emptied and the insertion counts are reset to 0. + """ + self._streaming_buffers_queue = {var["name"]: deque( + maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} + self._streaming_buffers_queue_insertion_counts = { + var["name"]: 0 for var in self.watcher_vars} + def load_data_from_file(self, filename): """ Loads data from a file saved through the saving_enabled function in start_streaming() or stream_n_values(). The file should contain a list of dicts, each dict containing a variable name and a list of values. The list of dicts should be separated by newlines. diff --git a/test/bela-test-send/render.cpp b/test/bela-test-send/render.cpp index fa1bd45..06461e9 100644 --- a/test/bela-test-send/render.cpp +++ b/test/bela-test-send/render.cpp @@ -5,6 +5,8 @@ Watcher myvar1("myvar1"); Watcher myvar2("myvar2"); +std::vector*> myVars = {&myvar1, &myvar2}; + struct ReceivedBuffer { uint32_t bufferId; char bufferType[4]; @@ -12,19 +14,16 @@ struct ReceivedBuffer { uint32_t empty; std::vector bufferData; }; - ReceivedBuffer receivedBuffer; uint receivedBufferHeaderSize; +uint64_t totalReceivedCount; -struct Buffer { +struct CallbackBuffer { uint32_t guiBufferId; std::vector bufferData; uint64_t count; }; - -Buffer buffer_1; -Buffer buffer_2; -uint64_t totalReceivedCount; +CallbackBuffer callbackBuffers[2]; bool binaryDataCallback(const std::string& addr, const WSServerDetails* id, const unsigned char* data, size_t size, void* arg) { @@ -37,20 +36,13 @@ bool binaryDataCallback(const std::string& addr, const WSServerDetails* id, cons printf("\ntotal received count: %llu, total data size: %zu, bufferId: %d, bufferType: %s, bufferLen: %d \n", totalReceivedCount, size, receivedBuffer.bufferId, receivedBuffer.bufferType, receivedBuffer.bufferLen); - if (receivedBuffer.bufferId == 0) { - buffer_1.bufferData = receivedBuffer.bufferData; - buffer_1.count++; - for (size_t i = 0; i < buffer_1.bufferData.size(); ++i) { - Bela_getDefaultWatcherManager()->tick(totalReceivedCount); - myvar1 = buffer_1.bufferData[i]; - } - - } else if (receivedBuffer.bufferId == 1) { - buffer_2.bufferData = receivedBuffer.bufferData; - buffer_2.count++; - for (size_t i = 0; i < buffer_1.bufferData.size(); ++i) { - Bela_getDefaultWatcherManager()->tick(totalReceivedCount); - myvar2 = buffer_2.bufferData[i]; + Bela_getDefaultWatcherManager()->tick(totalReceivedCount); + int _id = receivedBuffer.bufferId; + if (_id >= 0 && _id < myVars.size()) { + callbackBuffers[_id].bufferData = receivedBuffer.bufferData; + callbackBuffers[_id].count++; + for (size_t i = 0; i < callbackBuffers[_id].bufferData.size(); ++i) { + *myVars[_id] = callbackBuffers[_id].bufferData[i]; } } @@ -62,11 +54,12 @@ bool setup(BelaContext* context, void* userData) { Bela_getDefaultWatcherManager()->getGui().setup(context->projectName); Bela_getDefaultWatcherManager()->setup(context->audioSampleRate); // set sample rate in watcher - buffer_1.guiBufferId = Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); // for myvar - buffer_1.count = 0; - buffer_2.guiBufferId = Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); // for myvar2 - buffer_2.count = 0; - printf("dataBufferId_1: %d, dataBufferId_2: %d \n", buffer_1.guiBufferId, buffer_2.guiBufferId); + for (int i = 0; i < 2; ++i) { + callbackBuffers[i].guiBufferId = Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); + callbackBuffers[i].count = 0; + } + + printf("dataBufferId_1: %d, dataBufferId_2: %d \n", callbackBuffers[0].guiBufferId, callbackBuffers[1].guiBufferId); Bela_getDefaultWatcherManager()->getGui().setBinaryDataCallback(binaryDataCallback); diff --git a/test/test-send.py b/test/test-send.py index e1229d2..2c1df0c 100644 --- a/test/test-send.py +++ b/test/test-send.py @@ -11,7 +11,7 @@ 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! +# 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) diff --git a/test/test.py b/test/test.py index 245bba3..d890ef4 100644 --- a/test/test.py +++ b/test/test.py @@ -138,7 +138,8 @@ async def async_test_scheduling_streaming(): self.__test_buffers(mode="schedule") def test_on_data_callback(self): # TODO test with more than one var - variables = ["myvar", "myvar5"] # dense double + variables = ["myvar", "myvar5"] # dense double + stop_after = 10 async def async_test_on_data_callback(): @@ -146,23 +147,59 @@ async def async_test_on_data_callback(): self.streamer.start_streaming(variables, saving_enabled=False) timestamps = {var: [] for var in variables} + buffers = {var: [] for var in variables} def callback(buffer, var): - timestamps[var].append( - int(buffer["ref_timestamp"]/len(buffer["data"]))) + timestamps[var].append(buffer["ref_timestamp"]) + buffers[var].append(buffer["data"]) - self.streamer.on_data_callback(variables, callback, stop_after=10) + self.streamer.on_data_callback( + variables, callback, stop_after=stop_after) self.streamer.stop_streaming(variables) for var in variables: - self.assertEqual(len(timestamps[var]), 10, - "The callback should be called 10 times") - self.assertEqual(timestamps[var][-1] - timestamps[var][0], len(timestamps[var]) - 1, - "Timestamps are not continuous. The callback is missing some buffer") - + self.assertGreaterEqual(len(timestamps[var]), stop_after, + f'The callback should be called at least {stop_after} times') + for i in range(1, stop_after): + self.assertEqual(timestamps[var][i] - timestamps[var][i-1], 512, + "The timestamps should be continuous. The callback is missing some buffer") asyncio.run(async_test_on_data_callback()) + def test_on_block_callback(self): + variables = ["myvar", "myvar5"] # dense double + stop_after = 10 + + async def async_test_on_block_callback(): + self.streamer.start_streaming(variables, saving_enabled=False) + + timestamps = {var: [] for var in variables} + buffers = {var: [] for var in variables} + + def callback(block, vars): + for var in vars: + if block[var] == {}: + continue + timestamps[var].append(block[var]['ref_timestamp']) + buffers[var].append(block[var]['data']) + + + self.streamer.on_block_callback( + variables, callback, stop_after=stop_after) + + await asyncio.sleep(0.1) + + self.streamer.stop_streaming(variables) + + for var in variables: + self.assertGreaterEqual(len(timestamps[var]), stop_after, + f'The callback should be called at least {stop_after} times') + for i in range(1, stop_after): + self.assertEqual(timestamps[var][i] - timestamps[var][i-1], 512, + "The timestamps should be continuous. The callback is missing some buffer") + + asyncio.run(async_test_on_block_callback()) + class test_Logger(unittest.TestCase): @@ -437,8 +474,7 @@ def remove_file(file_path): if __name__ == '__main__': # run all tests - if 0: - unittest.main(verbosity=2) + # unittest.main(verbosity=2) # select which tests to run n = 1 @@ -446,31 +482,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_data_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) From 817425bcdd9c2b04224400c121cdc61616f60ceb Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Mon, 8 Jul 2024 14:45:08 +0200 Subject: [PATCH 06/29] added tasks before next release --- readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/readme.md b/readme.md index 755f072..a76a366 100644 --- a/readme.md +++ b/readme.md @@ -202,8 +202,17 @@ pipenv lock && pipenv sync # updates packages hashes pipenv run python -m build --sdist # builds the .tar.gz file ``` + ## To do and known issues +**Before next release** + +- [ ] **Add** a tutorial for `.send_buffer`, `.on_data_callback` and `.on_block_callback` +- [ ] **Check** what happens when `.on_data_callback` or `on_buffer_callback` are called before `start_streaming` + +**Long term** + +- [ ] **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) From f8e7d523fd822695c99173cdf13d545147b0cffb Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Tue, 9 Jul 2024 12:44:31 +0200 Subject: [PATCH 07/29] minor fixes --- pybela/Streamer.py | 15 ++++++--------- test/test.py | 48 +++++++++++++++++++++++----------------------- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/pybela/Streamer.py b/pybela/Streamer.py index 3dd6a5e..2e87585 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -461,9 +461,9 @@ async def async_on_data_callback(variables, callback, call_callback_per_variable _p_idx[var] = -1 + _p_count[var] _old_in_count[var] = _in_count[var] - while True: + while self.is_streaming(): # insertion count and streaming buffer should be copied at the same time - await asyncio.sleep(0.00001) + await asyncio.sleep(0.000001) _old_in_count = _in_count # _in_count = {var: value for (var, value) in self._streaming_buffers_queue_insertion_counts.items()} @@ -484,7 +484,7 @@ async def async_on_data_callback(variables, callback, call_callback_per_variable if _in_count[var] <= self.streaming_buffers_queue_length: _p_idx[var] += 1 else: - _p_idx[var] += 1 - _d_in_count + _p_idx[var] += 1 - _d_in_count[var] if _p_idx[var] < 0: _p_idx[var] = 0 @@ -495,7 +495,7 @@ async def async_on_data_callback(variables, callback, call_callback_per_variable # debug # print(var, "in_count", _in_count[var], "_d_in_count", - # _d_in_count, "p_count", _p_count[var], "_p_idx", _p_idx[var]) + # _d_in_count[var], "p_count", _p_count[var], "_p_idx", _p_idx[var]) # print(_p_idx[var], _buffer["ref_timestamp"], # _buffer["ref_timestamp"] // data_len[var]) @@ -518,9 +518,6 @@ async def async_on_data_callback(variables, callback, call_callback_per_variable else: callback(_block, variables, *args, **kwargs) - if not self.is_streaming(): - return # stop if streaming has been stopped - if stop_after > 0 and all(count >= stop_after for count in _rp_count.values()): print_info( f"on_data_callback stopped after processing {stop_after} buffers") @@ -717,11 +714,11 @@ def _process_data_msg(self, msg): msg, var_timestamp_mode, _type).copy() # 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 - _var_streaming_buffers_queue_insertion_counts = copy.deepcopy( + _var_streaming_buffers_queue_insertion_counts = copy.copy( self._streaming_buffers_queue_insertion_counts[var_name]) + 1 self._streaming_buffers_queue_insertion_counts[ var_name] = _var_streaming_buffers_queue_insertion_counts diff --git a/test/test.py b/test/test.py index d890ef4..39c2165 100644 --- a/test/test.py +++ b/test/test.py @@ -483,29 +483,29 @@ def remove_file(file_path): print(f"\n\n....Running test {i+1}/{n}") 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_data_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')) + 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_data_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) From 4980db024b5a5ae0672b278426fdf20d29dd5255 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Wed, 10 Jul 2024 13:14:31 +0200 Subject: [PATCH 08/29] added process-msg-worker and message queue --- pybela/Streamer.py | 27 +++++++++++++++------------ pybela/Watcher.py | 24 ++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/pybela/Streamer.py b/pybela/Streamer.py index 2e87585..b60814e 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -30,25 +30,26 @@ 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._streaming_buffers_queue_insertion_counts = {} 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() - + # -- 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 @@ -378,7 +379,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s return self.streaming_buffers_queue - def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list): + 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. @@ -396,8 +397,9 @@ def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list): format_str = buffer_type * len(data_list) binary_data = struct.pack(format_str, *data_list) self._send_msg(self.ws_data_add, idtypestr + binary_data) - print_info( - f"Sent buffer {buffer_id} of type {buffer_type} with length {buffer_length}...") + if verbose: + print_info( + f"Sent buffer {buffer_id} of type {buffer_type} with length {buffer_length}...") def on_data_callback(self, variables, callback, stop_after=0, *args, **kwargs): """ Run a callback on each buffer received. @@ -674,12 +676,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 @@ -697,7 +700,7 @@ def _process_data_msg(self, msg): _channel = int(_channel) assert _type in ['i', 'f', 'j', 'd', - 'c'], f"Unsupported type: {_type}" + 'c'], f"Unsupported type: {_type}" assert _type == self._watcher_vars[_channel][ 'type'], f"Type mismatch: {_type} != {self._watcher_vars[_channel]['type']}" diff --git a/pybela/Watcher.py b/pybela/Watcher.py index 20270da..9a6f52c 100644 --- a/pybela/Watcher.py +++ b/pybela/Watcher.py @@ -31,6 +31,10 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add self.ws_data_add = f"ws://{self.ip}:{self.port}/{self.data_add}" self.ws_ctrl = None self.ws_data = None + + + self._data_msg_queue = asyncio.Queue() + self._process_data_msg_worker_task = None self._list_response_available = asyncio.Event() self._list_response = None @@ -133,6 +137,7 @@ async def _async_connect(): # Connect to the data websocket self.ws_data = await websockets.connect(self.ws_data_add) + self._process_data_msg_worker_task = asyncio.create_task(self._process_data_msg_worker()) # Start listener loops self._start_listener(self.ws_ctrl, self.ws_ctrl_add) @@ -161,6 +166,7 @@ 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() + self._process_data_msg_worker_task.cancel() return asyncio.run(_async_stop()) def is_connected(self): @@ -204,7 +210,8 @@ 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) + await self._data_msg_queue.put(msg) + # self._process_data_msg(msg) elif ws_address == self.ws_ctrl_add: self._process_ctrl_msg(msg) else: @@ -238,13 +245,26 @@ async def _async_send_msg(ws_address, msg): # process messages - def _process_data_msg(self, msg): # method overwritten by streamer + async def _process_data_msg_worker(self): # method overwritten by streamer + """Process data message. This method is overwritten by the streamer. + + Args: + msg (str): Bytestring with data + """ + + while self.ws_data is not None and self.ws_data.open: + msg = await self._data_msg_queue.get() + await self._process_data_msg(msg) + self._data_msg_queue.task_done() + + async def _process_data_msg(self, msg): """Process data message. This method is overwritten by the streamer. Args: msg (str): Bytestring with data """ pass + def _process_ctrl_msg(self, msg): """Process control message From e8853ffcdab873704a86b33bb174ff77cf5a4921 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Wed, 10 Jul 2024 17:24:53 +0200 Subject: [PATCH 09/29] data to send is now managed in a asyncio queue --- pybela/Streamer.py | 2 +- pybela/Watcher.py | 33 +++++++++++++++++++++++---------- test/test-send.py | 2 ++ 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/pybela/Streamer.py b/pybela/Streamer.py index b60814e..bcccc24 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -465,7 +465,7 @@ async def async_on_data_callback(variables, callback, call_callback_per_variable while self.is_streaming(): # insertion count and streaming buffer should be copied at the same time - await asyncio.sleep(0.000001) + await asyncio.sleep(0.0001) _old_in_count = _in_count # _in_count = {var: value for (var, value) in self._streaming_buffers_queue_insertion_counts.items()} diff --git a/pybela/Watcher.py b/pybela/Watcher.py index 9a6f52c..f0bb8d8 100644 --- a/pybela/Watcher.py +++ b/pybela/Watcher.py @@ -33,9 +33,12 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add self.ws_data = None - self._data_msg_queue = asyncio.Queue() - self._process_data_msg_worker_task = None - + self._received_data_msg_queue = asyncio.Queue() + self._process_received_data_msg_worker_task = None + + self._to_send_data_msg_queue = asyncio.Queue() + self._sending_data_msg_worker_task = None + self._list_response_available = asyncio.Event() self._list_response = None self._log_response_available = asyncio.Event() @@ -137,7 +140,8 @@ async def _async_connect(): # Connect to the data websocket self.ws_data = await websockets.connect(self.ws_data_add) - self._process_data_msg_worker_task = asyncio.create_task(self._process_data_msg_worker()) + 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) @@ -166,7 +170,8 @@ 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() - self._process_data_msg_worker_task.cancel() + self._process_received_data_msg_worker_task.cancel() + self._sending_data_msg_worker_task.cancel() return asyncio.run(_async_stop()) def is_connected(self): @@ -210,7 +215,7 @@ async def _async_start_listener(ws, ws_address): if self._printall_responses: print(msg) if ws_address == self.ws_data_add: - await self._data_msg_queue.put(msg) + await self._received_data_msg_queue.put(msg) # self._process_data_msg(msg) elif ws_address == self.ws_ctrl_add: self._process_ctrl_msg(msg) @@ -235,13 +240,21 @@ def _send_msg(self, ws_address, msg): 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(msg) + await 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(msg) except Exception as e: 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): + 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 @@ -252,10 +265,10 @@ async def _process_data_msg_worker(self): # method overwritten by streamer msg (str): Bytestring with data """ - while self.ws_data is not None and self.ws_data.open: - msg = await self._data_msg_queue.get() + 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._data_msg_queue.task_done() + self._received_data_msg_queue.task_done() async def _process_data_msg(self, msg): """Process data message. This method is overwritten by the streamer. diff --git a/test/test-send.py b/test/test-send.py index 2c1df0c..3281dfe 100644 --- a/test/test-send.py +++ b/test/test-send.py @@ -38,6 +38,8 @@ def test_send_buffer(self): 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) From 748f3ec2f97b400814e1ad7bd653ba755b03032e Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 11 Jul 2024 15:40:49 +0200 Subject: [PATCH 10/29] added processed_data_msg queue, callbacks are now called on start_streaming, fixes oscillating latency. added process_data_msg and _send_data_msg workers. also added list response queue. in the logger, all create_task now needed to be inside of an async function. removed async functions from test --- pybela/Logger.py | 77 ++++---- pybela/Streamer.py | 241 +++++++++--------------- pybela/Watcher.py | 58 +++--- readme.md | 12 +- test/test.py | 458 +++++++++++++++++++++------------------------ 5 files changed, 376 insertions(+), 470 deletions(-) diff --git a/pybela/Logger.py b/pybela/Logger.py index e334912..61bdbab 100644 --- a/pybela/Logger.py +++ b/pybela/Logger.py @@ -46,17 +46,20 @@ 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} @@ -157,25 +160,18 @@ 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( @@ -375,8 +371,9 @@ async def async_copy_file_in_chunks(remote_path, local_path, chunk_size=2**12): 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. @@ -561,19 +558,23 @@ def _action_on_all_bin_files_in_project(self, action, local_dir=None): # 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 bcccc24..1ccb518 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -1,3 +1,4 @@ +import asyncio.base_subprocess import json import copy import os @@ -30,16 +31,22 @@ 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) - + # -- 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._streaming_buffers_queue_insertion_counts = {} self.last_streamed_buffer = {} + # -- 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 @@ -90,8 +97,6 @@ def streaming_buffers_queue_length(self, value): self._streaming_buffers_queue_length = value self._streaming_buffers_queue = {var["name"]: deque( maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} # resize streaming buffer - self._streaming_buffers_queue_insertion_counts = { - var["name"]: 0 for var in self.watcher_vars} @property def streaming_buffers_queue(self): @@ -115,8 +120,6 @@ def start(self): 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} - self._streaming_buffers_queue_insertion_counts = { - var["name"]: 0 for var in self.watcher_vars} @property def streaming_buffers_data(self): @@ -134,7 +137,7 @@ 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): if self.is_streaming(): print_warning("Stopping previous streaming session...") @@ -150,10 +153,33 @@ 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 callback_workers(): + + if on_block_callback and on_buffer_callback: + print_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)) + + 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, variables)) + + asyncio.run(callback_workers()) + # checks types and if no variables are specified, stream all watcher variables (default) 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): """ Starts the streaming session. The session can be stopped with stop_streaming(). @@ -164,10 +190,12 @@ 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. """ variables = self.__streaming_common_routine( - variables, saving_enabled, saving_filename, saving_dir) + variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback) # 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) @@ -216,6 +244,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 @@ -236,10 +265,17 @@ async def async_stop_streaming(variables=[]): {"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}...") + 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): """Schedule streaming of variables. The streaming session can be stopped with stop_streaming(). Args: @@ -250,10 +286,12 @@ 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. """ variables = self.__streaming_common_routine( - variables, saving_enabled, saving_filename, saving_dir) + variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback) self._streaming_mode = "SCHEDULE" @@ -284,7 +322,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): """ 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). @@ -303,13 +341,15 @@ 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. 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)) - 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): """ Asynchronous version of stream_n_values(). Usage: stream_task = asyncio.create_task(streamer.async_stream_n_values(variables, n_values, saving_enabled, saving_filename)) @@ -324,6 +364,8 @@ 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. Returns: deque: Streaming buffers queue @@ -331,7 +373,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s # resizes the streaming buffer size to n_values and returns it when full variables = self.__streaming_common_routine( - variables=variables, saving_enabled=saving_enabled, saving_filename=saving_filename, saving_dir=saving_dir) + variables=variables, saving_enabled=saving_enabled, saving_filename=saving_filename, saving_dir=saving_dir, on_buffer_callback=on_buffer_callback, on_block_callback=on_block_callback) self._streaming_mode = "N_VALUES" # flag cleared in __rec_msg_callback @@ -379,6 +421,38 @@ 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): + 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() + if asyncio.iscoroutinefunction(on_buffer_callback): + await on_buffer_callback(msg) + else: + on_buffer_callback(msg) + + await asyncio.sleep(0.0001) + + async def __async_on_block_callback_worker(self, on_block_callback, 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): + if asyncio.iscoroutinefunction(on_block_callback): + await on_block_callback(msgs) + else: + on_block_callback(msgs) + + 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. @@ -401,133 +475,6 @@ def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list, verbose= print_info( f"Sent buffer {buffer_id} of type {buffer_type} with length {buffer_length}...") - def on_data_callback(self, variables, callback, stop_after=0, *args, **kwargs): - """ Run a callback on each buffer received. - - Args: - variables (list): List of variables to run the callback on. Important: the current implementation assumes that variables are of the same type and that buffers are of the same length (i.e. also same timestamping mode). Check the buffer length of each variable with get_prop_of_var(var, "data_length"). - callback (function): Function to be run on each buffer. It takes the buffer and the variable name as arguments. Callback can be a coroutine (async def ...). Example: - def callback(buffer, var): - (buffer is a dict and var is a string) - print(var, buffer["ref_timestamp"]) - print(buffer["data"].shape) - stop_after (int, optional): Stop after processing "stop_after" buffers. If 0, will run forever. Defaults to 0. - """ - self.__on_data_callback( - variables, callback, call_callback_per_variable=True, stop_after=stop_after, *args, **kwargs) - - def on_block_callback(self, variables, callback, stop_after=0, *args, **kwargs): - """ Run a callback on each block of buffers received. - - Args: - variables (list): List of variables to run the callback on. Important: the current implementation assumes that variables are of the same type and that buffers are of the same length (i.e. also same timestamping mode). Check the buffer length of each variable with get_prop_of_var(var, "data_length"). - callback (function): Function to be run on each block of buffers. It takes the block and the variables as arguments. Callback can be a coroutine (async def ...). Example: - def callback(block, variables): - (block is a dict and variables is a list of strings) - print(variables) - print(block.keys()) - stop_after (int, optional): Stop after processing "stop_after" buffers. If 0, will run forever. Defaults to 0. - """ - self.__on_data_callback( - variables, callback, call_callback_per_variable=False, stop_after=stop_after, *args, **kwargs) - - def __on_data_callback(self, variables, callback, call_callback_per_variable=True, stop_after=0, *args, **kwargs): - - data_len, data_type, timestamp_mode = [], [], [] - for var in variables: - data_len.append(self.get_prop_of_var(var, "data_length")) - data_type.append(self.get_prop_of_var(var, "type")) - timestamp_mode.append(self.get_prop_of_var(var, "timestamp_mode")) - assert len(set(data_len) - ) == 1, "Variables have different buffer sizes" - assert len(set(data_type)) == 1, "Variables have different data types" - assert len(set(timestamp_mode) - ) == 1, "Variables have different timestamp modes" - - data_len = {var: self.get_prop_of_var( - var, "data_length") for var in variables} - - async def async_on_data_callback(variables, callback, call_callback_per_variable=True, stop_after=stop_after, *args, **kwargs): - - # insertion count, processed count, real processed count (without init bias), processed index, old insertion count, insertion count delta - _in_count, _p_count, _rp_count, _p_idx, _old_in_count, _d_in_count = {}, {}, {}, {}, {}, {} - _rp_count = {var: 0 for var in variables} - _d_in_count = {var: 0 for var in variables} - for var in variables: - _in_count[var] = self._streaming_buffers_queue_insertion_counts[var] - _p_count[var] = _in_count[var] - - if _in_count[var] >= self.streaming_buffers_queue_length: - _p_idx[var] = self.streaming_buffers_queue_length - 2 - else: - _p_idx[var] = -1 + _p_count[var] - _old_in_count[var] = _in_count[var] - - while self.is_streaming(): - # insertion count and streaming buffer should be copied at the same time - await asyncio.sleep(0.0001) - - _old_in_count = _in_count - # _in_count = {var: value for (var, value) in self._streaming_buffers_queue_insertion_counts.items()} - # _queue = {var: value for (var, value) in self._streaming_buffers_queue.items()} - _in_count = copy.copy( - self._streaming_buffers_queue_insertion_counts) - _queue = copy.copy(self._streaming_buffers_queue) - - _block = {var: {} for var in variables} - for var in variables: - - _d_in_count[var] = _in_count[var] - _old_in_count[var] - - # if all buffers have been processed - if _in_count[var] == _p_count[var]: - continue - else: - if _in_count[var] <= self.streaming_buffers_queue_length: - _p_idx[var] += 1 - else: - _p_idx[var] += 1 - _d_in_count[var] - - if _p_idx[var] < 0: - _p_idx[var] = 0 - - _buffer = _queue[var][_p_idx[var]] - _block[var] = _buffer - _rp_count[var] += 1 - - # debug - # print(var, "in_count", _in_count[var], "_d_in_count", - # _d_in_count[var], "p_count", _p_count[var], "_p_idx", _p_idx[var]) - - # print(_p_idx[var], _buffer["ref_timestamp"], - # _buffer["ref_timestamp"] // data_len[var]) - - _p_count[var] += 1 - - if call_callback_per_variable: - if asyncio.iscoroutinefunction(callback): - # non-blocking - asyncio.run( - callback(_buffer, var, *args, **kwargs)) - else: - callback(_buffer, var, *args, **kwargs) - - # if call_per_block is enabled, if there are processed buffers and if there are new buffers - if not call_callback_per_variable and any(_rp_count.values()) and any(_d_in_count.values()): - if asyncio.iscoroutinefunction(callback): - asyncio.run(callback(_block, variables, * - args, **kwargs)) # non-blocking - else: - callback(_block, variables, *args, **kwargs) - - if stop_after > 0 and all(count >= stop_after for count in _rp_count.values()): - print_info( - f"on_data_callback stopped after processing {stop_after} buffers") - return - - asyncio.run(async_on_data_callback( - variables, callback, call_callback_per_variable, stop_after=stop_after)) - # - utils def is_streaming(self): @@ -543,8 +490,6 @@ def flush_queue(self): """ self._streaming_buffers_queue = {var["name"]: deque( maxlen=self._streaming_buffers_queue_length) for var in self.watcher_vars} - self._streaming_buffers_queue_insertion_counts = { - var["name"]: 0 for var in self.watcher_vars} def load_data_from_file(self, filename): """ @@ -700,7 +645,7 @@ async def _process_data_msg(self, msg): _channel = int(_channel) assert _type in ['i', 'f', 'j', 'd', - 'c'], f"Unsupported type: {_type}" + 'c'], f"Unsupported type: {_type}" assert _type == self._watcher_vars[_channel][ 'type'], f"Type mismatch: {_type} != {self._watcher_vars[_channel]['type']}" @@ -716,15 +661,15 @@ async 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.copy( self._streaming_buffers_queue[var_name]) _var_streaming_buffers_queue.append(parsed_buffer) self._streaming_buffers_queue[var_name] = _var_streaming_buffers_queue - _var_streaming_buffers_queue_insertion_counts = copy.copy( - self._streaming_buffers_queue_insertion_counts[var_name]) + 1 - self._streaming_buffers_queue_insertion_counts[ - var_name] = _var_streaming_buffers_queue_insertion_counts # populate last streamed buffer if self._mode == "STREAM": diff --git a/pybela/Watcher.py b/pybela/Watcher.py index f0bb8d8..52f0146 100644 --- a/pybela/Watcher.py +++ b/pybela/Watcher.py @@ -31,18 +31,17 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add self.ws_data_add = f"ws://{self.ip}:{self.port}/{self.data_add}" self.ws_ctrl = None self.ws_data = 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._list_response_available = asyncio.Event() - self._list_response = None - self._log_response_available = asyncio.Event() - self._log_response = None + self._sending_data_msg_worker_task = None + self._watcher_vars = None @@ -140,8 +139,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()) + 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) @@ -183,9 +184,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()) @@ -215,8 +217,7 @@ async def _async_start_listener(ws, ws_address): if self._printall_responses: print(msg) if ws_address == self.ws_data_add: - await self._received_data_msg_queue.put(msg) - # 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: @@ -240,17 +241,19 @@ def _send_msg(self, ws_address, msg): 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._to_send_data_msg_queue.put(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(msg) except Exception as e: 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) @@ -258,18 +261,18 @@ async def _send_data_msg_worker(self): # process messages - async def _process_data_msg_worker(self): # method overwritten by streamer - """Process data message. This method is overwritten by the 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. @@ -277,7 +280,6 @@ async def _process_data_msg(self, msg): msg (str): Bytestring with data """ pass - def _process_ctrl_msg(self, msg): """Process control message @@ -287,13 +289,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. diff --git a/readme.md b/readme.md index a76a366..5855ab1 100644 --- a/readme.md +++ b/readme.md @@ -2,12 +2,13 @@ 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 Bela to python. It also allows you to send buffers of data from python to Bela or control the value of variables in your Bela code from python. -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/`. 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. + +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. ### 1. Installing the python package @@ -101,7 +102,7 @@ pybela has three different modes of operation: - **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 `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 @@ -202,16 +203,15 @@ pipenv lock && pipenv sync # updates packages hashes pipenv run python -m build --sdist # builds the .tar.gz file ``` - ## To do and known issues **Before next release** -- [ ] **Add** a tutorial for `.send_buffer`, `.on_data_callback` and `.on_block_callback` -- [ ] **Check** what happens when `.on_data_callback` or `on_buffer_callback` are called before `start_streaming` +- [ ] **Add** a tutorial for `.send_buffer`, and data and buffers callback **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. diff --git a/test/test.py b/test/test.py index 39c2165..a97f792 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,111 +95,93 @@ 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_data_callback(self): # TODO test with more than one var + def test_on_buffer_callback(self): variables = ["myvar", "myvar5"] # dense double - stop_after = 10 - async def async_test_on_data_callback(): + # test only on vars of the same type - # test only on vars of the same type - self.streamer.start_streaming(variables, saving_enabled=False) + timestamps = {var: [] for var in variables} + buffers = {var: [] for var in variables} - 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"]) - def callback(buffer, var): - timestamps[var].append(buffer["ref_timestamp"]) - buffers[var].append(buffer["data"]) + self.streamer.start_streaming( + variables, saving_enabled=False, on_buffer_callback=callback) - self.streamer.on_data_callback( - variables, callback, stop_after=stop_after) + asyncio.run(asyncio.sleep(0.1)) - self.streamer.stop_streaming(variables) + self.streamer.stop_streaming(variables) - for var in variables: - self.assertGreaterEqual(len(timestamps[var]), stop_after, - f'The callback should be called at least {stop_after} times') - for i in range(1, stop_after): - self.assertEqual(timestamps[var][i] - timestamps[var][i-1], 512, - "The timestamps should be continuous. The callback is missing some buffer") - asyncio.run(async_test_on_data_callback()) + 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 - stop_after = 10 - - async def async_test_on_block_callback(): - self.streamer.start_streaming(variables, saving_enabled=False) - - timestamps = {var: [] for var in variables} - buffers = {var: [] for var in variables} - - def callback(block, vars): - for var in vars: - if block[var] == {}: - continue - timestamps[var].append(block[var]['ref_timestamp']) - buffers[var].append(block[var]['data']) - - - self.streamer.on_block_callback( - variables, callback, stop_after=stop_after) - - await asyncio.sleep(0.1) - - self.streamer.stop_streaming(variables) - - for var in variables: - self.assertGreaterEqual(len(timestamps[var]), stop_after, - f'The callback should be called at least {stop_after} times') - for i in range(1, stop_after): - self.assertEqual(timestamps[var][i] - timestamps[var][i-1], 512, - "The timestamps should be continuous. The callback is missing some buffer") - - asyncio.run(async_test_on_block_callback()) + + 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) + + 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): @@ -244,85 +227,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): @@ -339,81 +316,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): @@ -427,44 +395,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): @@ -491,7 +453,7 @@ def remove_file(file_path): test_Streamer('test_stream_n_values'), test_Streamer('test_start_stop_streaming'), test_Streamer('test_scheduling_streaming'), - test_Streamer('test_on_data_callback'), + test_Streamer('test_on_buffer_callback'), test_Streamer('test_on_block_callback'), # logger test_Logger('test_logged_files_with_transfer'), From 7f2f969f6f7f9b5567f782f0d2acd841c61e1226 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Sun, 14 Jul 2024 17:53:02 +0100 Subject: [PATCH 11/29] now catching exceptions from the callbacks, minor fix test --- pybela/Streamer.py | 85 +++++++++++++++++++++++++++++++--------------- test/test.py | 4 ++- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/pybela/Streamer.py b/pybela/Streamer.py index 1ccb518..a038fa1 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -137,7 +137,7 @@ def streaming_buffers_data(self): # - streaming methods - def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./", on_buffer_callback=None, on_block_callback=None): + 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...") @@ -155,31 +155,28 @@ def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_ self._processed_data_msg_queue = asyncio.Queue() # clear processed data queue - async def callback_workers(): + async def async_callback_workers(): if on_block_callback and on_buffer_callback: print_error( - "Both on_buffer_callback and on_block_callback cannot be enabled at the same time.") + "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)) + 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, variables)) + self.__async_on_block_callback_worker(on_block_callback, callback_args, variables)) - asyncio.run(callback_workers()) + asyncio.run(async_callback_workers()) # checks types and if no variables are specified, stream all watcher variables (default) return self._var_arg_checker(variables) - def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving_filename="var_stream.txt", saving_dir="./", on_buffer_callback=None, on_block_callback=None): + 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(). @@ -192,10 +189,12 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving 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 (). + """ variables = self.__streaming_common_routine( - variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback) + 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) @@ -275,7 +274,7 @@ async def async_stop_streaming(variables=[]): return asyncio.run(async_stop_streaming(variables)) - 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): + 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: @@ -288,10 +287,11 @@ def schedule_streaming(self, variables=[], timestamps=[], durations=[], saving_e 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 (). """ variables = self.__streaming_common_routine( - variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback) + variables, saving_enabled, saving_filename, saving_dir, on_buffer_callback, on_block_callback, callback_args) self._streaming_mode = "SCHEDULE" @@ -322,7 +322,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="./", on_buffer_callback=None, on_block_callback=None): + 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). @@ -343,13 +343,14 @@ def stream_n_values(self, variables=[], periods=[], n_values=1000, saving_enable 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="./", on_buffer_callback=None, on_block_callback=None): + 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)) @@ -366,6 +367,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s 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 @@ -373,7 +375,7 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s # resizes the streaming buffer size to n_values and returns it when full variables = self.__streaming_common_routine( - variables=variables, saving_enabled=saving_enabled, saving_filename=saving_filename, saving_dir=saving_dir, on_buffer_callback=on_buffer_callback, on_block_callback=on_block_callback) + 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 @@ -423,19 +425,33 @@ async def async_stream_n_values(self, variables=[], periods=[], n_values=1000, s # callbacks - async def __async_on_buffer_callback_worker(self, on_buffer_callback): + 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() - if asyncio.iscoroutinefunction(on_buffer_callback): - await on_buffer_callback(msg) - else: - on_buffer_callback(msg) + 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, variables): + 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: @@ -444,10 +460,25 @@ async def __async_on_block_callback_worker(self, on_block_callback, variables): msgs.append(msg) self._processed_data_msg_queue.task_done() if len(msgs) == len(variables): - if asyncio.iscoroutinefunction(on_block_callback): - await on_block_callback(msgs) - else: - on_block_callback(msgs) + 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) diff --git a/test/test.py b/test/test.py index a97f792..b32a490 100644 --- a/test/test.py +++ b/test/test.py @@ -177,7 +177,9 @@ def callback(block): 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, From 01217b106d3334f7372c61d0fab7266f438dab06 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Wed, 7 Aug 2024 15:57:25 +0100 Subject: [PATCH 12/29] added sphinx documentation site, made some methods private to avoid having them in the docs, added requirements.txt (generated on hook) --- .gitignore | 5 +- Pipfile | 5 +- Pipfile.lock | 1319 +++++++++++++++++++++++---------------- docs/_static/custom.css | 3 + docs/conf.py | 63 ++ docs/index.rst | 29 + docs/modules.rst | 25 + pybela/Controller.py | 8 +- pybela/Logger.py | 72 +-- pybela/Streamer.py | 42 +- pybela/Watcher.py | 27 +- pybela/utils.py | 18 +- readme.md | 30 +- requirements.txt | 131 ++++ 14 files changed, 1129 insertions(+), 648 deletions(-) create mode 100644 docs/_static/custom.css create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/modules.rst create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 9895b54..27a0e42 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,12 @@ dev/ *.err .vscode/ __pycache__/ -*.txt build/ *.egg-info *.bin *.fzz dist/ *.sh -.clang-format \ No newline at end of file +.clang-format +docs/_build/* +docs/readme.rst \ No newline at end of file diff --git a/Pipfile b/Pipfile index 10bb698..fb115d6 100644 --- a/Pipfile +++ b/Pipfile @@ -19,9 +19,12 @@ paramiko = "*" numpy = "==1.26" [dev-packages] -build = "*" twine = "*" pip-chill = "*" +sphinx = "*" +pandoc = "*" +sphinx-rtd-theme = "*" +build = "*" [scripts] test = "python test/test.py" diff --git a/Pipfile.lock b/Pipfile.lock index 0dff0eb..b1cfce2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4cd98bfbe97e6b9b8fedd7513069b48c2fce0c509a14a7d036c16822a2242bc9" + "sha256": "0dd6b2e7eb3b0610e07e6b43203a33b5cd65a70456e6738bb3771e5aa37d6aef" }, "pipfile-spec": 6, "requires": { @@ -100,11 +100,11 @@ }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "babel": { "hashes": [ @@ -116,36 +116,36 @@ }, "bcrypt": { "hashes": [ - "sha256:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64", - "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf", - "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05", - "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c", - "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15", - "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991", - "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623", - "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834", - "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08", - "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a", - "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74", - "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455", - "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3", - "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73", - "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611", - "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2", - "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d", - "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286", - "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978", - "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d", - "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc", - "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6", - "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed", - "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650", - "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84", - "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1", - "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a" + "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.7'", - "version": "==4.1.3" + "version": "==4.2.0" }, "beautifulsoup4": { "hashes": [ @@ -293,77 +293,92 @@ }, "bokeh": { "hashes": [ - "sha256:931a43ee59dbf1720383ab904f8205e126b85561aac55592415b800c96f1b0eb", - "sha256:a16d5cc0abb93d2d270d70fc35851f3e1b9208814a985a4678e0ba5ef2d9cd42" + "sha256:b7c22fb0f7004b04f12e1b7b26ee0269a26737a08ded848fb58f6a34ec1eb155", + "sha256:c6f33817f866fc67fbeb5df79cd13a8bb592c05c591f3fd7f4f22b824f7afa01" ], "index": "pypi", - "version": "==3.4.2" + "version": "==3.4.3" }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "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" + "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.16.0" + "version": "==1.17.0" }, "charset-normalizer": { "hashes": [ @@ -521,69 +536,64 @@ }, "cryptography": { "hashes": [ - "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad", - "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583", - "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b", - "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c", - "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1", - "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648", - "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949", - "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba", - "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c", - "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9", - "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d", - "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c", - "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e", - "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2", - "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d", - "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7", - "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70", - "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2", - "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7", - "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14", - "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe", - "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e", - "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71", - "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961", - "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7", - "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c", - "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28", - "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842", - "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902", - "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801", - "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a", - "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e" + "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": "==42.0.8" + "version": "==43.0.0" }, "debugpy": { "hashes": [ - "sha256:0600faef1d0b8d0e85c816b8bb0cb90ed94fc611f308d5fde28cb8b3d2ff0fe3", - "sha256:1523bc551e28e15147815d1397afc150ac99dbd3a8e64641d53425dba57b0ff9", - "sha256:15bc2f4b0f5e99bf86c162c91a74c0631dbd9cef3c6a1d1329c946586255e859", - "sha256:16c8dcab02617b75697a0a925a62943e26a0330da076e2a10437edd9f0bf3755", - "sha256:16e16df3a98a35c63c3ab1e4d19be4cbc7fdda92d9ddc059294f18910928e0ca", - "sha256:2cbd4d9a2fc5e7f583ff9bf11f3b7d78dfda8401e8bb6856ad1ed190be4281ad", - "sha256:3f8c3f7c53130a070f0fc845a0f2cee8ed88d220d6b04595897b66605df1edd6", - "sha256:40f062d6877d2e45b112c0bbade9a17aac507445fd638922b1a5434df34aed02", - "sha256:5a019d4574afedc6ead1daa22736c530712465c0c4cd44f820d803d937531b2d", - "sha256:5d3ccd39e4021f2eb86b8d748a96c766058b39443c1f18b2dc52c10ac2757835", - "sha256:62658aefe289598680193ff655ff3940e2a601765259b123dc7f89c0239b8cd3", - "sha256:7ee2e1afbf44b138c005e4380097d92532e1001580853a7cb40ed84e0ef1c3d2", - "sha256:7f8d57a98c5a486c5c7824bc0b9f2f11189d08d73635c326abef268f83950326", - "sha256:8a13417ccd5978a642e91fb79b871baded925d4fadd4dfafec1928196292aa0a", - "sha256:95378ed08ed2089221896b9b3a8d021e642c24edc8fef20e5d4342ca8be65c00", - "sha256:acdf39855f65c48ac9667b2801234fc64d46778021efac2de7e50907ab90c634", - "sha256:bd11fe35d6fd3431f1546d94121322c0ac572e1bfb1f6be0e9b8655fb4ea941e", - "sha256:c78ba1680f1015c0ca7115671fe347b28b446081dada3fedf54138f44e4ba031", - "sha256:cf327316ae0c0e7dd81eb92d24ba8b5e88bb4d1b585b5c0d32929274a66a5210", - "sha256:d3408fddd76414034c02880e891ea434e9a9cf3a69842098ef92f6e809d09afa", - "sha256:e24ccb0cd6f8bfaec68d577cb49e9c680621c336f347479b3fce060ba7c09ec1", - "sha256:f179af1e1bd4c88b0b9f0fa153569b24f6b6f3de33f94703336363ae62f4bf47" - ], - "markers": "python_version >= '3.8'", - "version": "==1.8.2" + "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.5" }, "decorator": { "hashes": [ @@ -603,11 +613,11 @@ }, "exceptiongroup": { "hashes": [ - "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad", - "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16" + "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", + "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc" ], "markers": "python_version < '3.11'", - "version": "==1.2.1" + "version": "==1.2.2" }, "executing": { "hashes": [ @@ -665,19 +675,19 @@ }, "importlib-metadata": { "hashes": [ - "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f", - "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812" + "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369", + "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d" ], "markers": "python_version < '3.10'", - "version": "==8.0.0" + "version": "==8.2.0" }, "ipykernel": { "hashes": [ - "sha256:1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da", - "sha256:3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c" + "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", + "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215" ], "index": "pypi", - "version": "==6.29.4" + "version": "==6.29.5" }, "ipython": { "hashes": [ @@ -735,11 +745,11 @@ }, "jsonschema": { "hashes": [ - "sha256:5b22d434a45935119af990552c862e5d6d564e8f6601206b305a61fdf661a2b7", - "sha256:ff4cfd6b1367a40e7bc6411caec72effadd3db0bbe5017de188f2d6108335802" + "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", + "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566" ], "markers": "python_version >= '3.8'", - "version": "==4.22.0" + "version": "==4.23.0" }, "jsonschema-specifications": { "hashes": [ @@ -808,11 +818,11 @@ }, "jupyter-server": { "hashes": [ - "sha256:12558d158ec7a0653bf96cc272bc7ad79e0127d503b982ed144399346694f726", - "sha256:16f7177c3a4ea8fe37784e2d31271981a812f0b2874af17339031dc3510cc2a5" + "sha256:47ff506127c2f7851a17bf4713434208fc490955d0e8632e95014a9a9afbeefd", + "sha256:66095021aa9638ced276c248b1d81862e4c50f292d575920bbe960de1c56b12b" ], "markers": "python_version >= '3.8'", - "version": "==2.14.1" + "version": "==2.14.2" }, "jupyter-server-terminals": { "hashes": [ @@ -824,11 +834,11 @@ }, "jupyterlab": { "hashes": [ - "sha256:0b59d11808e84bb84105c73364edfa867dd475492429ab34ea388a52f2e2e596", - "sha256:df6e46969ea51d66815167f23d92f105423b7f1f06fa604d4f44aeb018c82c7b" + "sha256:343a979fb9582fd08c8511823e320703281cd072a0049bcdafdc7afeda7f2537", + "sha256:807a7ec73637744f879e112060d4b9d9ebe028033b7a429b2d1f4fc523d00245" ], "markers": "python_version >= '3.8'", - "version": "==4.2.3" + "version": "==4.2.4" }, "jupyterlab-pygments": { "hashes": [ @@ -840,11 +850,11 @@ }, "jupyterlab-server": { "hashes": [ - "sha256:15cbb349dc45e954e09bacf81b9f9bcb10815ff660fb2034ecd7417db3a7ea27", - "sha256:54aa2d64fd86383b5438d9f0c032f043c4d8c0264b8af9f60bd061157466ea43" + "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4", + "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4" ], "markers": "python_version >= '3.8'", - "version": "==2.27.2" + "version": "==2.27.3" }, "jupyterlab-widgets": { "hashes": [ @@ -1123,11 +1133,11 @@ }, "panel": { "hashes": [ - "sha256:659e9fc5b495e6519c5d07e8148fa5eeed9bc648356ec83fc299381ba5a726ef", - "sha256:b49bb9676567b0c0730bf69348c057247080811aec56364dd4fcfba80e5e09a0" + "sha256:a6dbddd65e9e68c54a9b683f103b79b48fcea5dd9f463b81a783ea11520fe9cb", + "sha256:a7c9be109b57bdea16a143ce6a897500e1172a28b8a7c0dcfd5b7f61c616ea42" ], "index": "pypi", - "version": "==1.4.4" + "version": "==1.4.5" }, "param": { "hashes": [ @@ -1163,78 +1173,89 @@ }, "pillow": { "hashes": [ - "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c", - "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2", - "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb", - "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d", - "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa", - "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3", - "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1", - "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a", - "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd", - "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8", - "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999", - "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599", - "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936", - "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375", - "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d", - "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b", - "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60", - "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572", - "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3", - "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced", - "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f", - "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b", - "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19", - "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f", - "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d", - "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383", - "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795", - "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355", - "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57", - "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09", - "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b", - "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462", - "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf", - "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f", - "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a", - "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad", - "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9", - "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d", - "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45", - "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994", - "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d", - "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338", - "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463", - "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451", - "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591", - "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c", - "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd", - "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32", - "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9", - "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf", - "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5", - "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828", - "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3", - "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5", - "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2", - "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b", - "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2", - "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475", - "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3", - "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb", - "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef", - "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015", - "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002", - "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170", - "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84", - "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57", - "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f", - "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27", - "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a" - ], - "markers": "python_version >= '3.8'", - "version": "==10.3.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": [ @@ -1293,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, @@ -1359,162 +1380,185 @@ }, "pyviz-comms": { "hashes": [ - "sha256:31541b976a21b7738557c3ea23bd8e44e94e736b9ed269570dcc28db4449d7e3", - "sha256:3167df932656416c4bd711205dad47e986a3ebae1f316258ddc26f9e01513ef7" + "sha256:fd26951eebc7950106d481655d91ba06296d4cf352dffb1d03f88f959832448e", + "sha256:fde4a017c2213ecee63a9a6741431c845e42a5c7b1588e4a7ba2e4370c583728" ], "markers": "python_version >= '3.8'", - "version": "==3.0.2" + "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:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef", - "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:01fbfbeb8249a68d257f601deb50c70c929dc2dfe683b754659569e502fbd3aa", - "sha256:0270b49b6847f0d106d64b5086e9ad5dc8a902413b5dbbb15d12b60f9c1747a4", - "sha256:03c0ae165e700364b266876d712acb1ac02693acd920afa67da2ebb91a0b3c09", - "sha256:068ca17214038ae986d68f4a7021f97e187ed278ab6dccb79f837d765a54d753", - "sha256:082a2988364b60bb5de809373098361cf1dbb239623e39e46cb18bc035ed9c0c", - "sha256:0aaf982e68a7ac284377d051c742610220fd06d330dcd4c4dbb4cdd77c22a537", - "sha256:0c0991f5a96a8e620f7691e61178cd8f457b49e17b7d9cfa2067e2a0a89fc1d5", - "sha256:115f8359402fa527cf47708d6f8a0f8234f0e9ca0cab7c18c9c189c194dbf620", - "sha256:15c59e780be8f30a60816a9adab900c12a58d79c1ac742b4a8df044ab2a6d920", - "sha256:1b7d0e124948daa4d9686d421ef5087c0516bc6179fdcf8828b8444f8e461a77", - "sha256:1c8eb19abe87029c18f226d42b8a2c9efdd139d08f8bf6e085dd9075446db450", - "sha256:204e0f176fd1d067671157d049466869b3ae1fc51e354708b0dc41cf94e23a3a", - "sha256:2136f64fbb86451dbbf70223635a468272dd20075f988a102bf8a3f194a411dc", - "sha256:2b291d1230845871c00c8462c50565a9cd6026fe1228e77ca934470bb7d70ea0", - "sha256:2c18645ef6294d99b256806e34653e86236eb266278c8ec8112622b61db255de", - "sha256:2cc4e280098c1b192c42a849de8de2c8e0f3a84086a76ec5b07bfee29bda7d18", - "sha256:2ed8357f4c6e0daa4f3baf31832df8a33334e0fe5b020a61bc8b345a3db7a606", - "sha256:3191d312c73e3cfd0f0afdf51df8405aafeb0bad71e7ed8f68b24b63c4f36500", - "sha256:3401613148d93ef0fd9aabdbddb212de3db7a4475367f49f590c837355343972", - "sha256:34106f68e20e6ff253c9f596ea50397dbd8699828d55e8fa18bd4323d8d966e6", - "sha256:3516119f4f9b8671083a70b6afaa0a070f5683e431ab3dc26e9215620d7ca1ad", - "sha256:38ece17ec5f20d7d9b442e5174ae9f020365d01ba7c112205a4d59cf19dc38ee", - "sha256:3b4032a96410bdc760061b14ed6a33613ffb7f702181ba999df5d16fb96ba16a", - "sha256:3bf8b000a4e2967e6dfdd8656cd0757d18c7e5ce3d16339e550bd462f4857e59", - "sha256:3e3070e680f79887d60feeda051a58d0ac36622e1759f305a41059eff62c6da7", - "sha256:4496b1282c70c442809fc1b151977c3d967bfb33e4e17cedbf226d97de18f709", - "sha256:44dd6fc3034f1eaa72ece33588867df9e006a7303725a12d64c3dff92330f625", - "sha256:4adfbb5451196842a88fda3612e2c0414134874bffb1c2ce83ab4242ec9e027d", - "sha256:4b7c0c0b3244bb2275abe255d4a30c050d541c6cb18b870975553f1fb6f37527", - "sha256:4c82a6d952a1d555bf4be42b6532927d2a5686dd3c3e280e5f63225ab47ac1f5", - "sha256:5344b896e79800af86ad643408ca9aa303a017f6ebff8cee5a3163c1e9aec987", - "sha256:5bde86a2ed3ce587fa2b207424ce15b9a83a9fa14422dcc1c5356a13aed3df9d", - "sha256:5bf6c237f8c681dfb91b17f8435b2735951f0d1fad10cc5dfd96db110243370b", - "sha256:5dbb9c997932473a27afa93954bb77a9f9b786b4ccf718d903f35da3232317de", - "sha256:69ea9d6d9baa25a4dc9cef5e2b77b8537827b122214f210dd925132e34ae9b12", - "sha256:6b3146f9ae6af82c47a5282ac8803523d381b3b21caeae0327ed2f7ecb718798", - "sha256:6bcb34f869d431799c3ee7d516554797f7760cb2198ecaa89c3f176f72d062be", - "sha256:6ca08b840fe95d1c2bd9ab92dac5685f949fc6f9ae820ec16193e5ddf603c3b2", - "sha256:6ca7a9a06b52d0e38ccf6bca1aeff7be178917893f3883f37b75589d42c4ac20", - "sha256:703c60b9910488d3d0954ca585c34f541e506a091a41930e663a098d3b794c67", - "sha256:715bdf952b9533ba13dfcf1f431a8f49e63cecc31d91d007bc1deb914f47d0e4", - "sha256:72b67f966b57dbd18dcc7efbc1c7fc9f5f983e572db1877081f075004614fcdd", - "sha256:74423631b6be371edfbf7eabb02ab995c2563fee60a80a30829176842e71722a", - "sha256:77a85dca4c2430ac04dc2a2185c2deb3858a34fe7f403d0a946fa56970cf60a1", - "sha256:7821d44fe07335bea256b9f1f41474a642ca55fa671dfd9f00af8d68a920c2d4", - "sha256:788f15721c64109cf720791714dc14afd0f449d63f3a5487724f024345067381", - "sha256:7ca684ee649b55fd8f378127ac8462fb6c85f251c2fb027eb3c887e8ee347bcd", - "sha256:7daa3e1369355766dea11f1d8ef829905c3b9da886ea3152788dc25ee6079e02", - "sha256:7e6bc96ebe49604df3ec2c6389cc3876cabe475e6bfc84ced1bf4e630662cb35", - "sha256:80b12f25d805a919d53efc0a5ad7c0c0326f13b4eae981a5d7b7cc343318ebb7", - "sha256:871587bdadd1075b112e697173e946a07d722459d20716ceb3d1bd6c64bd08ce", - "sha256:88b88282e55fa39dd556d7fc04160bcf39dea015f78e0cecec8ff4f06c1fc2b5", - "sha256:8d7a498671ca87e32b54cb47c82a92b40130a26c5197d392720a1bce1b3c77cf", - "sha256:926838a535c2c1ea21c903f909a9a54e675c2126728c21381a94ddf37c3cbddf", - "sha256:971e8990c5cc4ddcff26e149398fc7b0f6a042306e82500f5e8db3b10ce69f84", - "sha256:9b273ecfbc590a1b98f014ae41e5cf723932f3b53ba9367cfb676f838038b32c", - "sha256:a42db008d58530efa3b881eeee4991146de0b790e095f7ae43ba5cc612decbc5", - "sha256:a72a84570f84c374b4c287183debc776dc319d3e8ce6b6a0041ce2e400de3f32", - "sha256:ac97a21de3712afe6a6c071abfad40a6224fd14fa6ff0ff8d0c6e6cd4e2f807a", - "sha256:acb704195a71ac5ea5ecf2811c9ee19ecdc62b91878528302dd0be1b9451cc90", - "sha256:b32bff85fb02a75ea0b68f21e2412255b5731f3f389ed9aecc13a6752f58ac97", - "sha256:b3cd31f859b662ac5d7f4226ec7d8bd60384fa037fc02aee6ff0b53ba29a3ba8", - "sha256:b63731993cdddcc8e087c64e9cf003f909262b359110070183d7f3025d1c56b5", - "sha256:b6907da3017ef55139cf0e417c5123a84c7332520e73a6902ff1f79046cd3b94", - "sha256:ba6e5e6588e49139a0979d03a7deb9c734bde647b9a8808f26acf9c547cab1bf", - "sha256:c1c8f2a2ca45292084c75bb6d3a25545cff0ed931ed228d3a1810ae3758f975f", - "sha256:ce828058d482ef860746bf532822842e0ff484e27f540ef5c813d516dd8896d2", - "sha256:d0a2d1bd63a4ad79483049b26514e70fa618ce6115220da9efdff63688808b17", - "sha256:d0cdde3c78d8ab5b46595054e5def32a755fc028685add5ddc7403e9f6de9879", - "sha256:d57dfbf9737763b3a60d26e6800e02e04284926329aee8fb01049635e957fe81", - "sha256:d8416c23161abd94cc7da80c734ad7c9f5dbebdadfdaa77dad78244457448223", - "sha256:dba7d9f2e047dfa2bca3b01f4f84aa5246725203d6284e3790f2ca15fba6b40a", - "sha256:dbf012d8fcb9f2cf0643b65df3b355fdd74fc0035d70bb5c845e9e30a3a4654b", - "sha256:e1258c639e00bf5e8a522fec6c3eaa3e30cf1c23a2f21a586be7e04d50c9acab", - "sha256:e222562dc0f38571c8b1ffdae9d7adb866363134299264a1958d077800b193b7", - "sha256:e4946d6bdb7ba972dfda282f9127e5756d4f299028b1566d1245fa0d438847e6", - "sha256:e746524418b70f38550f2190eeee834db8850088c834d4c8406fbb9bc1ae10b2", - "sha256:e76654e9dbfb835b3518f9938e565c7806976c07b37c33526b574cc1a1050480", - "sha256:e8918973fbd34e7814f59143c5f600ecd38b8038161239fd1a3d33d5817a38b8", - "sha256:e891ce81edd463b3b4c3b885c5603c00141151dd9c6936d98a680c8c72fe5c67", - "sha256:ebbbd0e728af5db9b04e56389e2299a57ea8b9dd15c9759153ee2455b32be6ad", - "sha256:eeb438a26d87c123bb318e5f2b3d86a36060b01f22fbdffd8cf247d52f7c9a2b", - "sha256:eed56b6a39216d31ff8cd2f1d048b5bf1700e4b32a01b14379c3b6dde9ce3aa3", - "sha256:f17cde1db0754c35a91ac00b22b25c11da6eec5746431d6e5092f0cd31a3fea9", - "sha256:f1a9b7d00fdf60b4039f4455afd031fe85ee8305b019334b72dcf73c567edc47", - "sha256:f4b6cecbbf3b7380f3b61de3a7b93cb721125dc125c854c14ddc91225ba52f83", - "sha256:f6b1d1c631e5940cac5a0b22c5379c86e8df6a4ec277c7a856b714021ab6cfad", - "sha256:f6c21c00478a7bea93caaaef9e7629145d4153b15a8653e8bb4609d4bc70dbfc" + "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.7'", - "version": "==26.0.3" + "version": "==26.1.0" }, "qtconsole": { "hashes": [ @@ -1566,108 +1610,112 @@ }, "rpds-py": { "hashes": [ - "sha256:05f3d615099bd9b13ecf2fc9cf2d839ad3f20239c678f461c753e93755d629ee", - "sha256:06d218939e1bf2ca50e6b0ec700ffe755e5216a8230ab3e87c059ebb4ea06afc", - "sha256:07f2139741e5deb2c5154a7b9629bc5aa48c766b643c1a6750d16f865a82c5fc", - "sha256:08d74b184f9ab6289b87b19fe6a6d1a97fbfea84b8a3e745e87a5de3029bf944", - "sha256:0abeee75434e2ee2d142d650d1e54ac1f8b01e6e6abdde8ffd6eeac6e9c38e20", - "sha256:154bf5c93d79558b44e5b50cc354aa0459e518e83677791e6adb0b039b7aa6a7", - "sha256:17c6d2155e2423f7e79e3bb18151c686d40db42d8645e7977442170c360194d4", - "sha256:1805d5901779662d599d0e2e4159d8a82c0b05faa86ef9222bf974572286b2b6", - "sha256:19ba472b9606c36716062c023afa2484d1e4220548751bda14f725a7de17b4f6", - "sha256:19e515b78c3fc1039dd7da0a33c28c3154458f947f4dc198d3c72db2b6b5dc93", - "sha256:1d54f74f40b1f7aaa595a02ff42ef38ca654b1469bef7d52867da474243cc633", - "sha256:207c82978115baa1fd8d706d720b4a4d2b0913df1c78c85ba73fe6c5804505f0", - "sha256:2625f03b105328729f9450c8badda34d5243231eef6535f80064d57035738360", - "sha256:27bba383e8c5231cd559affe169ca0b96ec78d39909ffd817f28b166d7ddd4d8", - "sha256:2c3caec4ec5cd1d18e5dd6ae5194d24ed12785212a90b37f5f7f06b8bedd7139", - "sha256:2cc7c1a47f3a63282ab0f422d90ddac4aa3034e39fc66a559ab93041e6505da7", - "sha256:2fc24a329a717f9e2448f8cd1f960f9dac4e45b6224d60734edeb67499bab03a", - "sha256:312fe69b4fe1ffbe76520a7676b1e5ac06ddf7826d764cc10265c3b53f96dbe9", - "sha256:32b7daaa3e9389db3695964ce8e566e3413b0c43e3394c05e4b243a4cd7bef26", - "sha256:338dee44b0cef8b70fd2ef54b4e09bb1b97fc6c3a58fea5db6cc083fd9fc2724", - "sha256:352a88dc7892f1da66b6027af06a2e7e5d53fe05924cc2cfc56495b586a10b72", - "sha256:35b2b771b13eee8729a5049c976197ff58a27a3829c018a04341bcf1ae409b2b", - "sha256:38e14fb4e370885c4ecd734f093a2225ee52dc384b86fa55fe3f74638b2cfb09", - "sha256:3c20f05e8e3d4fc76875fc9cb8cf24b90a63f5a1b4c5b9273f0e8225e169b100", - "sha256:3dd3cd86e1db5aadd334e011eba4e29d37a104b403e8ca24dcd6703c68ca55b3", - "sha256:489bdfe1abd0406eba6b3bb4fdc87c7fa40f1031de073d0cfb744634cc8fa261", - "sha256:48c2faaa8adfacefcbfdb5f2e2e7bdad081e5ace8d182e5f4ade971f128e6bb3", - "sha256:4a98a1f0552b5f227a3d6422dbd61bc6f30db170939bd87ed14f3c339aa6c7c9", - "sha256:4adec039b8e2928983f885c53b7cc4cda8965b62b6596501a0308d2703f8af1b", - "sha256:4e0ee01ad8260184db21468a6e1c37afa0529acc12c3a697ee498d3c2c4dcaf3", - "sha256:51584acc5916212e1bf45edd17f3a6b05fe0cbb40482d25e619f824dccb679de", - "sha256:531796fb842b53f2695e94dc338929e9f9dbf473b64710c28af5a160b2a8927d", - "sha256:5463c47c08630007dc0fe99fb480ea4f34a89712410592380425a9b4e1611d8e", - "sha256:5c45a639e93a0c5d4b788b2613bd637468edd62f8f95ebc6fcc303d58ab3f0a8", - "sha256:6031b25fb1b06327b43d841f33842b383beba399884f8228a6bb3df3088485ff", - "sha256:607345bd5912aacc0c5a63d45a1f73fef29e697884f7e861094e443187c02be5", - "sha256:618916f5535784960f3ecf8111581f4ad31d347c3de66d02e728de460a46303c", - "sha256:636a15acc588f70fda1661234761f9ed9ad79ebed3f2125d44be0862708b666e", - "sha256:673fdbbf668dd958eff750e500495ef3f611e2ecc209464f661bc82e9838991e", - "sha256:6afd80f6c79893cfc0574956f78a0add8c76e3696f2d6a15bca2c66c415cf2d4", - "sha256:6b5ff7e1d63a8281654b5e2896d7f08799378e594f09cf3674e832ecaf396ce8", - "sha256:6c4c4c3f878df21faf5fac86eda32671c27889e13570645a9eea0a1abdd50922", - "sha256:6cd8098517c64a85e790657e7b1e509b9fe07487fd358e19431cb120f7d96338", - "sha256:6d1e42d2735d437e7e80bab4d78eb2e459af48c0a46e686ea35f690b93db792d", - "sha256:6e30ac5e329098903262dc5bdd7e2086e0256aa762cc8b744f9e7bf2a427d3f8", - "sha256:70a838f7754483bcdc830444952fd89645569e7452e3226de4a613a4c1793fb2", - "sha256:720edcb916df872d80f80a1cc5ea9058300b97721efda8651efcd938a9c70a72", - "sha256:732672fbc449bab754e0b15356c077cc31566df874964d4801ab14f71951ea80", - "sha256:740884bc62a5e2bbb31e584f5d23b32320fd75d79f916f15a788d527a5e83644", - "sha256:7700936ef9d006b7ef605dc53aa364da2de5a3aa65516a1f3ce73bf82ecfc7ae", - "sha256:7732770412bab81c5a9f6d20aeb60ae943a9b36dcd990d876a773526468e7163", - "sha256:7750569d9526199c5b97e5a9f8d96a13300950d910cf04a861d96f4273d5b104", - "sha256:7f1944ce16401aad1e3f7d312247b3d5de7981f634dc9dfe90da72b87d37887d", - "sha256:81c5196a790032e0fc2464c0b4ab95f8610f96f1f2fa3d4deacce6a79852da60", - "sha256:8352f48d511de5f973e4f2f9412736d7dea76c69faa6d36bcf885b50c758ab9a", - "sha256:8927638a4d4137a289e41d0fd631551e89fa346d6dbcfc31ad627557d03ceb6d", - "sha256:8c7672e9fba7425f79019db9945b16e308ed8bc89348c23d955c8c0540da0a07", - "sha256:8d2e182c9ee01135e11e9676e9a62dfad791a7a467738f06726872374a83db49", - "sha256:910e71711d1055b2768181efa0a17537b2622afeb0424116619817007f8a2b10", - "sha256:942695a206a58d2575033ff1e42b12b2aece98d6003c6bc739fbf33d1773b12f", - "sha256:9437ca26784120a279f3137ee080b0e717012c42921eb07861b412340f85bae2", - "sha256:967342e045564cef76dfcf1edb700b1e20838d83b1aa02ab313e6a497cf923b8", - "sha256:998125738de0158f088aef3cb264a34251908dd2e5d9966774fdab7402edfab7", - "sha256:9e6934d70dc50f9f8ea47081ceafdec09245fd9f6032669c3b45705dea096b88", - "sha256:a3d456ff2a6a4d2adcdf3c1c960a36f4fd2fec6e3b4902a42a384d17cf4e7a65", - "sha256:a7b28c5b066bca9a4eb4e2f2663012debe680f097979d880657f00e1c30875a0", - "sha256:a888e8bdb45916234b99da2d859566f1e8a1d2275a801bb8e4a9644e3c7e7909", - "sha256:aa3679e751408d75a0b4d8d26d6647b6d9326f5e35c00a7ccd82b78ef64f65f8", - "sha256:aaa71ee43a703c321906813bb252f69524f02aa05bf4eec85f0c41d5d62d0f4c", - "sha256:b646bf655b135ccf4522ed43d6902af37d3f5dbcf0da66c769a2b3938b9d8184", - "sha256:b906b5f58892813e5ba5c6056d6a5ad08f358ba49f046d910ad992196ea61397", - "sha256:b9bb1f182a97880f6078283b3505a707057c42bf55d8fca604f70dedfdc0772a", - "sha256:bd1105b50ede37461c1d51b9698c4f4be6e13e69a908ab7751e3807985fc0346", - "sha256:bf18932d0003c8c4d51a39f244231986ab23ee057d235a12b2684ea26a353590", - "sha256:c273e795e7a0f1fddd46e1e3cb8be15634c29ae8ff31c196debb620e1edb9333", - "sha256:c69882964516dc143083d3795cb508e806b09fc3800fd0d4cddc1df6c36e76bb", - "sha256:c827576e2fa017a081346dce87d532a5310241648eb3700af9a571a6e9fc7e74", - "sha256:cbfbea39ba64f5e53ae2915de36f130588bba71245b418060ec3330ebf85678e", - "sha256:ce0bb20e3a11bd04461324a6a798af34d503f8d6f1aa3d2aa8901ceaf039176d", - "sha256:d0cee71bc618cd93716f3c1bf56653740d2d13ddbd47673efa8bf41435a60daa", - "sha256:d21be4770ff4e08698e1e8e0bce06edb6ea0626e7c8f560bc08222880aca6a6f", - "sha256:d31dea506d718693b6b2cffc0648a8929bdc51c70a311b2770f09611caa10d53", - "sha256:d44607f98caa2961bab4fa3c4309724b185b464cdc3ba6f3d7340bac3ec97cc1", - "sha256:d58ad6317d188c43750cb76e9deacf6051d0f884d87dc6518e0280438648a9ac", - "sha256:d70129cef4a8d979caa37e7fe957202e7eee8ea02c5e16455bc9808a59c6b2f0", - "sha256:d85164315bd68c0806768dc6bb0429c6f95c354f87485ee3593c4f6b14def2bd", - "sha256:d960de62227635d2e61068f42a6cb6aae91a7fe00fca0e3aeed17667c8a34611", - "sha256:dc48b479d540770c811fbd1eb9ba2bb66951863e448efec2e2c102625328e92f", - "sha256:e1735502458621921cee039c47318cb90b51d532c2766593be6207eec53e5c4c", - "sha256:e2be6e9dd4111d5b31ba3b74d17da54a8319d8168890fbaea4b9e5c3de630ae5", - "sha256:e4c39ad2f512b4041343ea3c7894339e4ca7839ac38ca83d68a832fc8b3748ab", - "sha256:ed402d6153c5d519a0faf1bb69898e97fb31613b49da27a84a13935ea9164dfc", - "sha256:ee17cd26b97d537af8f33635ef38be873073d516fd425e80559f4585a7b90c43", - "sha256:f3027be483868c99b4985fda802a57a67fdf30c5d9a50338d9db646d590198da", - "sha256:f5bab211605d91db0e2995a17b5c6ee5edec1270e46223e513eaa20da20076ac", - "sha256:f6f8e3fecca256fefc91bb6765a693d96692459d7d4c644660a9fff32e517843", - "sha256:f7afbfee1157e0f9376c00bb232e80a60e59ed716e3211a80cb8506550671e6e", - "sha256:fa242ac1ff583e4ec7771141606aafc92b361cd90a05c30d93e343a0c2d82a89", - "sha256:fab6ce90574645a0d6c58890e9bcaac8d94dff54fb51c69e5522a7358b80ab64" + "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.18.1" + "version": "==0.20.0" }, "send2trash": { "hashes": [ @@ -1679,11 +1727,11 @@ }, "setuptools": { "hashes": [ - "sha256:937a48c7cdb7a21eb53cd7f9b59e525503aa8abaf3584c730dc5f7a5bec3a650", - "sha256:a58a8fde0541dab0419750bcc521fbdf8585f6e5cb41909df3a472ef7b81ca95" + "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1", + "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec" ], "markers": "python_version >= '3.8'", - "version": "==70.1.1" + "version": "==72.1.0" }, "six": { "hashes": [ @@ -1759,11 +1807,11 @@ }, "tqdm": { "hashes": [ - "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644", - "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb" + "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", + "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad" ], "markers": "python_version >= '3.7'", - "version": "==4.66.4" + "version": "==4.66.5" }, "traitlets": { "hashes": [ @@ -1953,6 +2001,22 @@ } }, "develop": { + "alabaster": { + "hashes": [ + "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65", + "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92" + ], + "markers": "python_version >= '3.9'", + "version": "==0.7.16" + }, + "babel": { + "hashes": [ + "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb", + "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413" + ], + "markers": "python_version >= '3.8'", + "version": "==2.15.0" + }, "backports.tarfile": { "hashes": [ "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", @@ -1971,11 +2035,11 @@ }, "certifi": { "hashes": [ - "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516", - "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56" + "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b", + "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90" ], "markers": "python_version >= '3.6'", - "version": "==2024.6.2" + "version": "==2024.7.4" }, "charset-normalizer": { "hashes": [ @@ -2075,11 +2139,11 @@ }, "docutils": { "hashes": [ - "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", - "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2" + "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6", + "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b" ], - "markers": "python_version >= '3.9'", - "version": "==0.21.2" + "markers": "python_version >= '3.7'", + "version": "==0.20.1" }, "idna": { "hashes": [ @@ -2089,13 +2153,21 @@ "markers": "python_version >= '3.5'", "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:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f", - "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812" + "sha256:11901fa0c2f97919b288679932bb64febaeacf289d18ac84dd68cb2e74213369", + "sha256:72e8d4399996132204f9a16dcc751af254a48f8d1b20b9ff0f98d4a8f901e73d" ], "markers": "python_version < '3.10'", - "version": "==8.0.0" + "version": "==8.2.0" }, "jaraco.classes": { "hashes": [ @@ -2115,19 +2187,27 @@ }, "jaraco.functools": { "hashes": [ - "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664", - "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8" + "sha256:3460c74cd0d32bf82b9576bbb3527c4364d5b27a21f5158a62aed6c4b42e23f5", + "sha256:c9d16a3ed4ccb5a889ad8e0b7a343401ee5b2a71cee6ed192d3f68bc351e94e3" ], "markers": "python_version >= '3.8'", - "version": "==4.0.1" + "version": "==4.0.2" + }, + "jinja2": { + "hashes": [ + "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369", + "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.4" }, "keyring": { "hashes": [ - "sha256:2458681cdefc0dbc0b7eb6cf75d0b98e59f9ad9b2d4edd319d18f68bdca95e50", - "sha256:daaffd42dbda25ddafb1ad5fec4024e5bbcfe424597ca1ca452b299861e49f1b" + "sha256:8d85a1ea5d6db8515b59e1c5d1d1678b03cf7fc8b8dcfb1651e8c4a524eb42ef", + "sha256:8d963da00ccdf06e356acd9bf3b743208878751032d8599c6cc89eb51310ffae" ], "markers": "python_version >= '3.8'", - "version": "==25.2.1" + "version": "==25.3.0" }, "markdown-it-py": { "hashes": [ @@ -2137,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", @@ -2147,32 +2293,32 @@ }, "more-itertools": { "hashes": [ - "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463", - "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320" + "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27", + "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923" ], "markers": "python_version >= '3.8'", - "version": "==10.3.0" + "version": "==10.4.0" }, "nh3": { "hashes": [ - "sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a", - "sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911", - "sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb", - "sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a", - "sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc", - "sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028", - "sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9", - "sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3", - "sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351", - "sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10", - "sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71", - "sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f", - "sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b", - "sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a", - "sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062", - "sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a" - ], - "version": "==0.2.17" + "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": [ @@ -2182,6 +2328,13 @@ "markers": "python_version >= '3.8'", "version": "==24.1" }, + "pandoc": { + "hashes": [ + "sha256:ecd1f8cbb7f4180c6b5db4a17a7c1a74df519995f5f186ef81ce72a9cbd0dd9a" + ], + "index": "pypi", + "version": "==2.4" + }, "pip-chill": { "hashes": [ "sha256:42c3b888efde0b3dc5d5307b92fae5fb67695dd9c29c9d31891b9505dd8b735a", @@ -2198,6 +2351,21 @@ "markers": "python_version >= '3.6'", "version": "==1.10.0" }, + "plumbum": { + "hashes": [ + "sha256:6092c85ab970b7a7a9d5d85c75200bc93be82b33c9bdf640ffa87d2d7c8709f0", + "sha256:8595d36dae2472587d6f59789c8d7b26250f45f6f6ed75ccb378de59ee7b9cf9" + ], + "markers": "python_version >= '3.6'", + "version": "==1.8.3" + }, + "ply": { + "hashes": [ + "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", + "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce" + ], + "version": "==3.11" + }, "pygments": { "hashes": [ "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", @@ -2254,6 +2422,85 @@ "markers": "python_full_version >= '3.7.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": [ "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 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/index.rst b/docs/index.rst new file mode 100644 index 0000000..f7bc2cc --- /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! You can find the + +.. 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 61bdbab..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,19 +46,20 @@ def start_logging(self, variables=[], transfer=True, logging_dir="./"): local_paths = {} if transfer: - async def copying_tasks(): # FIXME can we remove this async? + 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) + local_paths[var] = self._generate_local_filename( + local_path) copying_task = self.__copy_file_in_chunks( - remote_paths[var], local_paths[var]) + 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} @@ -99,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 @@ -174,7 +175,7 @@ def __logging_common_routine(self, mode, timestamps=[], durations=[], variables= remote_files[var] = list_res["watchers"][idx]["logFileName"] remote_paths[var] = f'/root/Bela/projects/{self.project_name}/{remote_files[var]}' - print_info( + _print_info( f"Started logging variables {variables}... Run stop_logging() to stop logging.") return remote_paths @@ -194,7 +195,7 @@ async def async_stop_logging(variables=[]): self.send_ctrl_msg( {"watcher": [{"cmd": "unlog", "watchers": variables}]}) - print_info(f"Stopped logging variables {variables}...") + _print_info(f"Stopped logging variables {variables}...") await asyncio.gather(*self._active_copying_tasks, return_exceptions=True) self._active_copying_tasks.clear() @@ -223,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 @@ -299,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 { @@ -342,7 +343,7 @@ async def async_copy_file_in_chunks(remote_path, local_path, chunk_size=2**12): break # Break the loop if the remote file size is non-zero except FileNotFoundError: - print_error( + _print_error( f"Remote file '{remote_path}' does not exist.") return None @@ -355,25 +356,24 @@ async def async_copy_file_in_chunks(remote_path, local_path, chunk_size=2**12): await asyncio.sleep(0.1) # flushed data break await local_file.write(chunk) - print_ok( + _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.") + _print_ok("Done.") except Exception as e: - print_error( + _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. @@ -406,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() @@ -430,14 +430,14 @@ 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 @@ -454,12 +454,12 @@ def finish_copying_file(self, remote_path, local_path): # TODO test remote_file_size = self.sftp_client.stat( remote_path).st_size except FileNotFoundError: - print_error( + _print_error( f"Remote file '{remote_path}' does not exist.") self.disconnect_ssh() return None if not os.path.exists(local_path): - print_error( + _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 @@ -473,17 +473,17 @@ def finish_copying_file(self, remote_path, local_path): # TODO test chunks = [(local_file_size, remaining_size)] data = remote_file.readv(chunks) - print_ok( + _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.") + _print_ok("Done.") else: - print_error( + _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}") + _print_error(f"Error finishing file copy: {e}") self.disconnect_ssh() @@ -513,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() @@ -526,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 @@ -538,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 @@ -553,12 +553,13 @@ 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 = [] - async def _async_action_action_on_all_bin_files_in_project(): # FIXME can we avoid this async? + + 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}" @@ -572,9 +573,8 @@ async def _async_action_action_on_all_bin_files_in_project(): # FIXME can we avo 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 a038fa1..b9df106 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -16,7 +16,7 @@ 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): @@ -113,7 +113,7 @@ def start(self): """ # self.connect() if not self.is_connected(): - print_warning( + _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( @@ -140,7 +140,7 @@ def streaming_buffers_data(self): 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 @@ -158,7 +158,7 @@ def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_ async def async_callback_workers(): if on_block_callback and on_buffer_callback: - print_error( + _print_error( "Error: Both on_buffer_callback and on_block_callback cannot be enabled at the same time.") return 0 if on_buffer_callback: @@ -214,7 +214,7 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving self.send_ctrl_msg( {"watcher": [{"cmd": "watch", "watchers": variables}]}) # asyncio.run(async_wait_for_streaming_to_start()) - print_info( + _print_info( f"Started streaming variables {variables}... Run stop_streaming() to stop streaming.") elif self._mode == "MONITOR": periods = self._check_periods(periods, variables) @@ -222,10 +222,10 @@ def start_streaming(self, variables=[], periods=[], saving_enabled=False, saving {"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=[]): """ @@ -258,12 +258,12 @@ 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: @@ -308,12 +308,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) @@ -399,7 +399,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": @@ -409,7 +409,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 @@ -446,7 +446,7 @@ async def __async_on_buffer_callback_worker(self, on_buffer_callback, callback_a else: on_buffer_callback(msg) except Exception as e: - print_error( + _print_error( f"Error in on_buffer_callback: {e}") await asyncio.sleep(0.0001) @@ -477,7 +477,7 @@ async def __async_on_block_callback_worker(self, on_block_callback, callback_arg on_block_callback(msgs) except Exception as e: - print_error( + _print_error( f"Error in on_block_callback: {e}") await asyncio.sleep(0.001) @@ -503,7 +503,7 @@ def send_buffer(self, buffer_id, buffer_type, buffer_length, data_list, verbose= binary_data = struct.pack(format_str, *data_list) self._send_msg(self.ws_data_add, idtypestr + binary_data) if verbose: - print_info( + _print_info( f"Sent buffer {buffer_id} of type {buffer_type} with length {buffer_length}...") # - utils @@ -543,7 +543,7 @@ def load_data_from_file(self, filename): except EOFError: # reached end of file break except Exception as e: - print_error(f"Error while loading data from file: {e}") + _print_error(f"Error while loading data from file: {e}") return None return data @@ -629,7 +629,7 @@ def plot_data(self, x_var, y_vars, y_range=None, plot_update_delay=100, rollover # check that x_var and y_vars are either streamed or monitored for _var in [x_var, *y_vars]: if not (_var in [var["name"] for var in self.watched_vars] or _var in [var["name"] for var in self.monitored_vars]): - print_error( + _print_error( f"PlottingError: {_var} is not being streamed or monitored.") return @@ -641,7 +641,7 @@ async def wait_for_streaming_buffers_to_arrive(): await asyncio.sleep(0.1) asyncio.run(wait_for_streaming_buffers_to_arrive()) if len(y_vars) > 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 @@ -761,7 +761,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()) @@ -812,7 +812,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 52f0146..404be90 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: @@ -31,7 +31,7 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add self.ws_data_add = f"ws://{self.ip}:{self.port}/{self.data_add}" self.ws_ctrl = None self.ws_data = None - + self._list_response_queue = asyncio.Queue() # receive data message queue @@ -41,7 +41,6 @@ def __init__(self, ip="192.168.7.2", port=5555, data_add="gui_data", control_add # send data message queue self._to_send_data_msg_queue = asyncio.Queue() self._sending_data_msg_worker_task = None - self._watcher_vars = None @@ -115,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 @@ -153,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)}.") @@ -224,7 +223,7 @@ async def _async_start_listener(ws, ws_address): 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)) @@ -245,7 +244,7 @@ async def _async_send_msg(ws_address, 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(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)) @@ -415,7 +414,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 @@ -513,9 +512,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: @@ -528,4 +527,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 5855ab1..b2f7c80 100644 --- a/readme.md +++ b/readme.md @@ -6,43 +6,23 @@ Below, you can find instructions to install pybela. You can find code examples a 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) +## Installation and set up 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. ### 1. Installing the python package -#### Option A: - -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 -``` - -#### Option C: - -You can also install this library using [pipenv](https://pipenv.pypa.io/en/latest/installation/) by cloning this repository and running: - -```bash -git clone --recurse-submodules https://github.com/BelaPlatform/pybela -cd pybela -pipenv install -``` - ### 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 internat If your Bela is connected to internet, you can ssh into your Bela (`ssh root@bela.local`) and change the branch: @@ -53,7 +33,7 @@ git checkout dev git pull ``` -#### 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: @@ -106,7 +86,7 @@ You can check the **tutorials** at tutorials/`for more detailed information and ### 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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d8a4f52 --- /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.15.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 From bfe6cd81ca1debd989e5899f16dd9d48693e9342 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Wed, 7 Aug 2024 16:12:39 +0100 Subject: [PATCH 13/29] typos --- docs/index.rst | 2 +- readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index f7bc2cc..3f77d08 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -5,7 +5,7 @@ pybela docs =========== -Welcome to pybela’s documentation! You can find the +Welcome to pybela’s documentation! .. include:: readme.rst diff --git a/readme.md b/readme.md index b2f7c80..979c22b 100644 --- a/readme.md +++ b/readme.md @@ -22,7 +22,7 @@ pip install pybela In order to use pybela, you will need to use the `dev` branch of the Bela code. -#### Option A: Bela connected to internat +#### 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: From d08f1653f95f49a68842069cee85445db097782e Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 14:57:36 +0100 Subject: [PATCH 14/29] github actions for docs deploy + minor changes in readmes --- .github/workflows/deploy-docs.yml | 36 +++++++++++++++++++++++++++++++ readme.md | 12 +++++++++-- setup.py | 5 +++-- 3 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/deploy-docs.yml diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..8474a88 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,36 @@ +name: Deploy Documentation to GitHub Pages + +on: + push: + branches: + - dev # Change this to your default branch if it's not '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.x' # Specify the Python version you need + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pipenv + pipenv install --dev + + - name: Build documentation + run: | + pipenv 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: ./_build/html \ No newline at end of file diff --git a/readme.md b/readme.md index 979c22b..60b213e 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,14 @@ pip install pybela ### 2. Set the Bela branch to `dev` -In order to use pybela, you will need to use the `dev` branch of the Bela code. +`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. + +**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 +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). #### Option A: Bela connected to internet @@ -30,7 +37,7 @@ 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: Bela not connected to internet @@ -59,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. diff --git a/setup.py b/setup.py index 37dddc0..e57824c 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ version="0.1.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", From 12cef088e8ce22597b98e39febfbc64deb1aacd8 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 14:59:13 +0100 Subject: [PATCH 15/29] specify python version to 3.9 --- .github/workflows/deploy-docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 8474a88..097e768 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.x' # Specify the Python version you need + python-version: '3.9' # Specify the Python version you need - name: Install dependencies run: | From 58228a04d4e5e7a23c4b1bd38f4431ce602db056 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 15:01:50 +0100 Subject: [PATCH 16/29] pandoc error - run pip-chill --- .github/workflows/deploy-docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 097e768..f5f90c4 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -22,7 +22,8 @@ jobs: run: | python -m pip install --upgrade pip pip install pipenv - pipenv install --dev + pipenv install -d + pipenv run pip-chill - name: Build documentation run: | From f9bcee2c5c5296cc7084f38d5bd4e6cf18b16ac8 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 15:06:30 +0100 Subject: [PATCH 17/29] pipenv not running proper environment, venv in project set to true --- .github/workflows/deploy-docs.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index f5f90c4..05b5cad 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -19,6 +19,8 @@ jobs: python-version: '3.9' # Specify the Python version you need - name: Install dependencies + env: + PIPENV_VENV_IN_PROJECT: true run: | python -m pip install --upgrade pip pip install pipenv From 52ffe12dfb0bb4f1f5929e325b4b8ba4f4b6ad66 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 15:08:04 +0100 Subject: [PATCH 18/29] all in the same step --- .github/workflows/deploy-docs.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 05b5cad..bbaf86b 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -18,17 +18,13 @@ jobs: with: python-version: '3.9' # Specify the Python version you need - - name: Install dependencies + - name: Install dependencies and build docs env: PIPENV_VENV_IN_PROJECT: true run: | python -m pip install --upgrade pip pip install pipenv pipenv install -d - pipenv run pip-chill - - - name: Build documentation - run: | pipenv run pandoc -s readme.md -o docs/readme.rst pipenv run sphinx-build -M html docs/ docs/_build From 479855074236a4fed489d8e188cb6fac0be75922 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 15:10:21 +0100 Subject: [PATCH 19/29] using pipenv shell instead of pipenv run --- .github/workflows/deploy-docs.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index bbaf86b..de679ec 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -18,15 +18,20 @@ jobs: with: python-version: '3.9' # Specify the Python version you need - - name: Install dependencies and build docs + - name: Install dependencies env: PIPENV_VENV_IN_PROJECT: true run: | python -m pip install --upgrade pip pip install pipenv pipenv install -d - pipenv run pandoc -s readme.md -o docs/readme.rst - pipenv run sphinx-build -M html docs/ docs/_build + pipenv run pip-chill + + - name: Build documentation + run: | + pipenv shell + pandoc -s readme.md -o docs/readme.rst + sphinx-build -M html docs/ docs/_build - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 From b0f152b1e613a8b8746b7e11b1472657b2150a2d Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:04:45 +0100 Subject: [PATCH 20/29] use venv path --- .github/workflows/deploy-docs.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index de679ec..182c037 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -29,9 +29,8 @@ jobs: - name: Build documentation run: | - pipenv shell - pandoc -s readme.md -o docs/readme.rst - sphinx-build -M html docs/ docs/_build + ./venv/bin/pandoc -s readme.md -o docs/readme.rst + ./venv/bin/sphinx-build -M html docs/ docs/_build - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 From ff6a2f4a4afba2a28e480e79a4fb895dd1445e58 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:07:50 +0100 Subject: [PATCH 21/29] wrong path --- .github/workflows/deploy-docs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 182c037..cdf4dae 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -25,12 +25,12 @@ jobs: python -m pip install --upgrade pip pip install pipenv pipenv install -d - pipenv run pip-chill + echo "pwd:!!" && pwd - name: Build documentation run: | - ./venv/bin/pandoc -s readme.md -o docs/readme.rst - ./venv/bin/sphinx-build -M html docs/ docs/_build + pipenv 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 From 207827acb52c1147b1b147c3de06f8be0cea0cd6 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:14:51 +0100 Subject: [PATCH 22/29] manually activate venv --- .github/workflows/deploy-docs.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index cdf4dae..2daa2d0 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -18,19 +18,20 @@ jobs: with: python-version: '3.9' # Specify the Python version you need - - name: Install dependencies + - name: Install dependencies and activate virtual environment env: PIPENV_VENV_IN_PROJECT: true run: | python -m pip install --upgrade pip pip install pipenv pipenv install -d - echo "pwd:!!" && pwd + export VENV_HOME_DIR=$(pipenv --venv) + source $VENV_HOME_DIR/bin/activate - name: Build documentation run: | - pipenv run pandoc -s readme.md -o docs/readme.rst - pipenv run sphinx-build -M html docs/ docs/_build + pandoc -s readme.md -o docs/readme.rst + sphinx-build -M html docs/ docs/_build - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 From c7657146799c24f61dcfbfe2b8f107986a3f43c1 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:19:23 +0100 Subject: [PATCH 23/29] using github env --- .github/workflows/deploy-docs.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 2daa2d0..357ab34 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -18,20 +18,20 @@ jobs: with: python-version: '3.9' # Specify the Python version you need - - name: Install dependencies and activate virtual environment + - name: Install dependencies env: PIPENV_VENV_IN_PROJECT: true run: | python -m pip install --upgrade pip pip install pipenv pipenv install -d - export VENV_HOME_DIR=$(pipenv --venv) - source $VENV_HOME_DIR/bin/activate + echo "VENV_HOME_DIR=$(pipenv --venv)" >> $GITHUB_ENV - name: Build documentation run: | - pandoc -s readme.md -o docs/readme.rst - sphinx-build -M html docs/ docs/_build + ${{ env.VENV_HOME_DIR }}/bin/pandoc -s readme.md -o docs/readme.rst + ${{ env.VENV_HOME_DIR }}/bin/sphinx-build -M html docs/ docs/_build + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 From 7ce7982ee65389710111f2594c83e769e18226cb Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:29:40 +0100 Subject: [PATCH 24/29] using pypandoc instead of pandoc --- .github/workflows/deploy-docs.yml | 4 +-- Pipfile | 2 +- Pipfile.lock | 44 +++++++++++-------------------- docs/conf.py | 2 ++ 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 357ab34..a96aa6a 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -25,12 +25,10 @@ jobs: python -m pip install --upgrade pip pip install pipenv pipenv install -d - echo "VENV_HOME_DIR=$(pipenv --venv)" >> $GITHUB_ENV - name: Build documentation run: | - ${{ env.VENV_HOME_DIR }}/bin/pandoc -s readme.md -o docs/readme.rst - ${{ env.VENV_HOME_DIR }}/bin/sphinx-build -M html docs/ docs/_build + pipenv run sphinx-build -M html docs/ docs/_build - name: Deploy to GitHub Pages diff --git a/Pipfile b/Pipfile index fb115d6..6a8d6c0 100644 --- a/Pipfile +++ b/Pipfile @@ -22,9 +22,9 @@ numpy = "==1.26" twine = "*" pip-chill = "*" sphinx = "*" -pandoc = "*" sphinx-rtd-theme = "*" build = "*" +pypandoc = "*" [scripts] test = "python test/test.py" diff --git a/Pipfile.lock b/Pipfile.lock index b1cfce2..96d005f 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "0dd6b2e7eb3b0610e07e6b43203a33b5cd65a70456e6738bb3771e5aa37d6aef" + "sha256": "71a1b0acb42a7239ac213a2ec848a69b25834a118de18424ad2f97e6f6c113d4" }, "pipfile-spec": 6, "requires": { @@ -108,11 +108,11 @@ }, "babel": { "hashes": [ - "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb", - "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413" + "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", + "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" ], "markers": "python_version >= '3.8'", - "version": "==2.15.0" + "version": "==2.16.0" }, "bcrypt": { "hashes": [ @@ -2011,11 +2011,11 @@ }, "babel": { "hashes": [ - "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb", - "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413" + "sha256:368b5b98b37c06b7daf6696391c3240c938b37767d4584413e8438c5c435fa8b", + "sha256:d1f3554ca26605fe173f3de0c65f750f5a42f924499bf134de6423582298e316" ], "markers": "python_version >= '3.8'", - "version": "==2.15.0" + "version": "==2.16.0" }, "backports.tarfile": { "hashes": [ @@ -2328,13 +2328,6 @@ "markers": "python_version >= '3.8'", "version": "==24.1" }, - "pandoc": { - "hashes": [ - "sha256:ecd1f8cbb7f4180c6b5db4a17a7c1a74df519995f5f186ef81ce72a9cbd0dd9a" - ], - "index": "pypi", - "version": "==2.4" - }, "pip-chill": { "hashes": [ "sha256:42c3b888efde0b3dc5d5307b92fae5fb67695dd9c29c9d31891b9505dd8b735a", @@ -2351,21 +2344,6 @@ "markers": "python_version >= '3.6'", "version": "==1.10.0" }, - "plumbum": { - "hashes": [ - "sha256:6092c85ab970b7a7a9d5d85c75200bc93be82b33c9bdf640ffa87d2d7c8709f0", - "sha256:8595d36dae2472587d6f59789c8d7b26250f45f6f6ed75ccb378de59ee7b9cf9" - ], - "markers": "python_version >= '3.6'", - "version": "==1.8.3" - }, - "ply": { - "hashes": [ - "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", - "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce" - ], - "version": "==3.11" - }, "pygments": { "hashes": [ "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", @@ -2374,6 +2352,14 @@ "markers": "python_version >= '3.8'", "version": "==2.18.0" }, + "pypandoc": { + "hashes": [ + "sha256:31652073c7960c2b03570bd1e94f602ca9bc3e70099df5ead4cea98ff5151c1e", + "sha256:4c7d71bf2f1ed122aac287113b5c4d537a33bbc3c1df5aed11a7d4a7ac074681" + ], + "index": "pypi", + "version": "==1.13" + }, "pyproject-hooks": { "hashes": [ "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965", diff --git a/docs/conf.py b/docs/conf.py index 9e1f15f..b78b877 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,9 @@ import os import sys import re +import pypandoc sys.path.insert(0, os.path.abspath('../pybela')) +pypandoc.convert_file('../readme.md', 'rst', outputfile="readme.rst") author = 'Teresa Pelinski' copyright = '2024' From fcc968ba79e41d70aa87446605b409dca8e635e5 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:36:56 +0100 Subject: [PATCH 25/29] install pandoc with apt-get --- .github/workflows/deploy-docs.yml | 2 ++ Pipfile | 1 - Pipfile.lock | 10 +--------- docs/conf.py | 2 -- requirements.txt | 2 +- 5 files changed, 4 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index a96aa6a..01a9fea 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -25,9 +25,11 @@ jobs: python -m pip install --upgrade pip pip install pipenv pipenv install -d + apt-get install pandoc - name: Build documentation run: | + pandoc -s readme.md -o docs/readme.rst pipenv run sphinx-build -M html docs/ docs/_build diff --git a/Pipfile b/Pipfile index 6a8d6c0..1a180d1 100644 --- a/Pipfile +++ b/Pipfile @@ -24,7 +24,6 @@ pip-chill = "*" sphinx = "*" sphinx-rtd-theme = "*" build = "*" -pypandoc = "*" [scripts] test = "python test/test.py" diff --git a/Pipfile.lock b/Pipfile.lock index 96d005f..12c6c3c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "71a1b0acb42a7239ac213a2ec848a69b25834a118de18424ad2f97e6f6c113d4" + "sha256": "642dced66ad7678ff331766e41cb1c33d791a67b2d4949bb99ddf003ec615cdf" }, "pipfile-spec": 6, "requires": { @@ -2352,14 +2352,6 @@ "markers": "python_version >= '3.8'", "version": "==2.18.0" }, - "pypandoc": { - "hashes": [ - "sha256:31652073c7960c2b03570bd1e94f602ca9bc3e70099df5ead4cea98ff5151c1e", - "sha256:4c7d71bf2f1ed122aac287113b5c4d537a33bbc3c1df5aed11a7d4a7ac074681" - ], - "index": "pypi", - "version": "==1.13" - }, "pyproject-hooks": { "hashes": [ "sha256:4b37730834edbd6bd37f26ece6b44802fb1c1ee2ece0e54ddff8bfc06db86965", diff --git a/docs/conf.py b/docs/conf.py index b78b877..9e1f15f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,9 +9,7 @@ import os import sys import re -import pypandoc sys.path.insert(0, os.path.abspath('../pybela')) -pypandoc.convert_file('../readme.md', 'rst', outputfile="readme.rst") author = 'Teresa Pelinski' copyright = '2024' diff --git a/requirements.txt b/requirements.txt index d8a4f52..31de848 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ arrow==1.3.0 asttokens==2.4.1 async-lru==2.0.4 attrs==24.2.0 -babel==2.15.0 +babel==2.16.0 bcrypt==4.2.0 beautifulsoup4==4.12.3 bitarray==2.9.2 From e782b04e2755a4c63e1c92632060845261726fe8 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:39:22 +0100 Subject: [PATCH 26/29] missing sudo --- .github/workflows/deploy-docs.yml | 2 +- docs/docs-readme.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/docs-readme.md diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 01a9fea..b700086 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -25,7 +25,7 @@ jobs: python -m pip install --upgrade pip pip install pipenv pipenv install -d - apt-get install pandoc + sudo apt-get update && sudo apt-get install -y pandoc - name: Build documentation run: | diff --git a/docs/docs-readme.md b/docs/docs-readme.md new file mode 100644 index 0000000..e69de29 From bb7a75dadee644acd295c7fb9a8ef5d15ac8d802 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:50:06 +0100 Subject: [PATCH 27/29] fixed build path + instructions to build docs --- .github/workflows/deploy-docs.yml | 2 +- docs/docs-readme.md | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index b700086..e8c6bfb 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -37,4 +37,4 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./_build/html \ No newline at end of file + publish_dir: ./docs/_build/html \ No newline at end of file diff --git a/docs/docs-readme.md b/docs/docs-readme.md index e69de29..e50f9bb 100644 --- a/docs/docs-readme.md +++ 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 docs/_build +pandoc -s readme.md -o docs/readme.rst +pipenv run sphinx-build -M html docs/ docs/_build +``` From 3e2aaa7d861560bd3e5a7818e4f6c411b9236eee Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Thu, 8 Aug 2024 16:56:50 +0100 Subject: [PATCH 28/29] docs only rebuilt if push to main, added docs link to readme, updated testing commit --- .github/workflows/deploy-docs.yml | 4 ++-- readme.md | 2 +- test/readme.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index e8c6bfb..943c015 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -3,7 +3,7 @@ name: Deploy Documentation to GitHub Pages on: push: branches: - - dev # Change this to your default branch if it's not 'main' + - main jobs: build: @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.9' # Specify the Python version you need + python-version: '3.9' - name: Install dependencies env: diff --git a/readme.md b/readme.md index 60b213e..a74c99b 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,7 @@ 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 Bela to python. It also allows you to send buffers of data from python to Bela or control the value of variables in your Bela code from python. -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). diff --git a/test/readme.md b/test/readme.md index 253e2b5..a03c5c7 100644 --- a/test/readme.md +++ b/test/readme.md @@ -1,6 +1,6 @@ # 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). From 02722acad139776365496203d4b0a9f5f52d6670 Mon Sep 17 00:00:00 2001 From: Teresa Pelinski Date: Fri, 9 Aug 2024 22:40:16 +0100 Subject: [PATCH 29/29] v1.0.0! added tutorial bela2python2bela, minor fixes --- docs/docs-readme.md | 6 +- pybela/Streamer.py | 23 +- pybela/Watcher.py | 2 + readme.md | 10 +- setup.py | 2 +- test/bela-test-send/render.cpp | 18 +- test/readme.md | 14 +- .../bela-code/bela2python2bela/Watcher.cpp | 1 + .../bela-code/bela2python2bela/Watcher.h | 1 + .../bela-code/bela2python2bela/render.cpp | 137 ++++++++++ tutorials/bela-code/potentiometers/sketch.js | 1 - tutorials/bela-code/timestamping/sketch.js | 1 - ...=> 1_Streamer-Bela-to-python-basics.ipynb} | 53 +--- .../2_Streamer-Bela-to-python-advanced.ipynb | 208 +++++++++++++++ .../notebooks/2_Streamer-python-to-Bela.ipynb | 64 ----- .../notebooks/3_Streamer-python-to-Bela.ipynb | 245 ++++++++++++++++++ .../{3_Monitor.ipynb => 4_Monitor.ipynb} | 13 +- .../{4_Logger.ipynb => 5_Logger.ipynb} | 4 +- .../notebooks/5_Sparse-timestamping.ipynb | 1 - tutorials/notebooks/6_Controller.ipynb | 4 +- .../notebooks/7_Sparse-timestamping.ipynb | 1 + 21 files changed, 649 insertions(+), 160 deletions(-) create mode 120000 tutorials/bela-code/bela2python2bela/Watcher.cpp create mode 120000 tutorials/bela-code/bela2python2bela/Watcher.h create mode 100644 tutorials/bela-code/bela2python2bela/render.cpp delete mode 120000 tutorials/bela-code/potentiometers/sketch.js delete mode 120000 tutorials/bela-code/timestamping/sketch.js rename tutorials/notebooks/{1_Streamer-Bela-to-python.ipynb => 1_Streamer-Bela-to-python-basics.ipynb} (90%) create mode 100644 tutorials/notebooks/2_Streamer-Bela-to-python-advanced.ipynb delete mode 100644 tutorials/notebooks/2_Streamer-python-to-Bela.ipynb create mode 100644 tutorials/notebooks/3_Streamer-python-to-Bela.ipynb rename tutorials/notebooks/{3_Monitor.ipynb => 4_Monitor.ipynb} (96%) rename tutorials/notebooks/{4_Logger.ipynb => 5_Logger.ipynb} (97%) delete mode 100644 tutorials/notebooks/5_Sparse-timestamping.ipynb create mode 100644 tutorials/notebooks/7_Sparse-timestamping.ipynb diff --git a/docs/docs-readme.md b/docs/docs-readme.md index e50f9bb..77d187b 100644 --- a/docs/docs-readme.md +++ b/docs/docs-readme.md @@ -3,7 +3,7 @@ To build the docs you will need to install `pandoc` to convert the `readme.md` i Then you can build the docs with: ```bash -rm -r docs/_build -pandoc -s readme.md -o docs/readme.rst -pipenv run sphinx-build -M html docs/ docs/_build +rm -r _build +pandoc -s ../readme.md -o readme.rst +pipenv run sphinx-build -M html . _build ``` diff --git a/pybela/Streamer.py b/pybela/Streamer.py index b9df106..69ec068 100644 --- a/pybela/Streamer.py +++ b/pybela/Streamer.py @@ -108,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. @@ -143,8 +130,14 @@ def __streaming_common_routine(self, variables=[], saving_enabled=False, saving_ _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) diff --git a/pybela/Watcher.py b/pybela/Watcher.py index 404be90..88f9599 100644 --- a/pybela/Watcher.py +++ b/pybela/Watcher.py @@ -170,7 +170,9 @@ 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()) diff --git a/readme.md b/readme.md index a74c99b..e52472e 100644 --- a/readme.md +++ b/readme.md @@ -1,10 +1,10 @@ # 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 Bela to python. It also allows you to send buffers of data from python to Bela or control the value of variables in your Bela code from python. +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/`. 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 @@ -85,7 +85,7 @@ 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 Bela to python (**NEW: or vice versa!** check the [tutorial](tutorials/notebooks/2_Streamer-python-to-Bela.ipynb)). +- **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. @@ -193,10 +193,6 @@ pipenv run python -m build --sdist # builds the .tar.gz file ## To do and known issues -**Before next release** - -- [ ] **Add** a tutorial for `.send_buffer`, and data and buffers callback - **Long term** - [ ] **Design**: remove nest_asyncio? diff --git a/setup.py b/setup.py index e57824c..3525073 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ 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 stream values to a Bela program from your laptop.", diff --git a/test/bela-test-send/render.cpp b/test/bela-test-send/render.cpp index 06461e9..65ba8e1 100644 --- a/test/bela-test-send/render.cpp +++ b/test/bela-test-send/render.cpp @@ -18,12 +18,6 @@ ReceivedBuffer receivedBuffer; uint receivedBufferHeaderSize; uint64_t totalReceivedCount; -struct CallbackBuffer { - uint32_t guiBufferId; - std::vector bufferData; - uint64_t count; -}; -CallbackBuffer callbackBuffers[2]; bool binaryDataCallback(const std::string& addr, const WSServerDetails* id, const unsigned char* data, size_t size, void* arg) { @@ -39,10 +33,9 @@ bool binaryDataCallback(const std::string& addr, const WSServerDetails* id, cons Bela_getDefaultWatcherManager()->tick(totalReceivedCount); int _id = receivedBuffer.bufferId; if (_id >= 0 && _id < myVars.size()) { - callbackBuffers[_id].bufferData = receivedBuffer.bufferData; - callbackBuffers[_id].count++; - for (size_t i = 0; i < callbackBuffers[_id].bufferData.size(); ++i) { - *myVars[_id] = callbackBuffers[_id].bufferData[i]; + + for (size_t i = 0; i < receivedBuffer.bufferData.size(); ++i) { + *myVars[_id] = receivedBuffer.bufferData[i]; } } @@ -55,12 +48,9 @@ bool setup(BelaContext* context, void* userData) { Bela_getDefaultWatcherManager()->setup(context->audioSampleRate); // set sample rate in watcher for (int i = 0; i < 2; ++i) { - callbackBuffers[i].guiBufferId = Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); - callbackBuffers[i].count = 0; + Bela_getDefaultWatcherManager()->getGui().setBuffer('f', 1024); } - printf("dataBufferId_1: %d, dataBufferId_2: %d \n", callbackBuffers[0].guiBufferId, callbackBuffers[1].guiBufferId); - Bela_getDefaultWatcherManager()->getGui().setBinaryDataCallback(binaryDataCallback); receivedBufferHeaderSize = sizeof(receivedBuffer.bufferId) + sizeof(receivedBuffer.bufferType) + sizeof(receivedBuffer.bufferLen) + sizeof(receivedBuffer.empty); diff --git a/test/readme.md b/test/readme.md index a03c5c7..d443dab 100644 --- a/test/readme.md +++ b/test/readme.md @@ -7,7 +7,7 @@ The watcher code is already included in `bela-test`. You can update your Bela AP 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/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-Bela-to-python.ipynb b/tutorials/notebooks/1_Streamer-Bela-to-python-basics.ipynb similarity index 90% rename from tutorials/notebooks/1_Streamer-Bela-to-python.ipynb rename to tutorials/notebooks/1_Streamer-Bela-to-python-basics.ipynb index 6f24776..09ac92c 100644 --- a/tutorials/notebooks/1_Streamer-Bela-to-python.ipynb +++ b/tutorials/notebooks/1_Streamer-Bela-to-python-basics.ipynb @@ -4,11 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela Tutorial 1: Streamer – Bela to python\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. \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:" ] }, @@ -37,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", @@ -89,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" ] }, { @@ -265,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", @@ -300,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": {}, 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/2_Streamer-python-to-Bela.ipynb b/tutorials/notebooks/2_Streamer-python-to-Bela.ipynb deleted file mode 100644 index ff63df0..0000000 --- a/tutorials/notebooks/2_Streamer-python-to-Bela.ipynb +++ /dev/null @@ -1,64 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# pybela Tutorial 2: 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. \n", - "\n", - "In this tutorial we will be looking at sending data from python to Bela. In this case, the routine is quite simple as the only available functionality is sending a buffer of a certain type and size (the `streamer.send_buffer()` method).\n", - "\n", - "To run this tutorial, first 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.)" - ] - }, - { - "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/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/3_Monitor.ipynb b/tutorials/notebooks/4_Monitor.ipynb similarity index 96% rename from tutorials/notebooks/3_Monitor.ipynb rename to tutorials/notebooks/4_Monitor.ipynb index d32b366..5602d99 100644 --- a/tutorials/notebooks/3_Monitor.ipynb +++ b/tutorials/notebooks/4_Monitor.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# pybela tutorial 3: 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_Logger.ipynb b/tutorials/notebooks/5_Logger.ipynb similarity index 97% rename from tutorials/notebooks/4_Logger.ipynb rename to tutorials/notebooks/5_Logger.ipynb index 9cb55a0..853ad24 100644 --- a/tutorials/notebooks/4_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:" ] }, diff --git a/tutorials/notebooks/5_Sparse-timestamping.ipynb b/tutorials/notebooks/5_Sparse-timestamping.ipynb deleted file mode 100644 index 5f4b98a..0000000 --- a/tutorials/notebooks/5_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.9.16"},"orig_nbformat":4},"nbformat":4,"nbformat_minor":2} diff --git a/tutorials/notebooks/6_Controller.ipynb b/tutorials/notebooks/6_Controller.ipynb index d4e9bfc..8ae8270 100644 --- a/tutorials/notebooks/6_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}