From 1f3c4bbbf0037eaf4e6a9e2d5738e2ef5d3d1604 Mon Sep 17 00:00:00 2001 From: jiawei <47899566+Jiaweihu08@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:36:25 +0200 Subject: [PATCH] Issue 264: Update qviz for multiblock files (#437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update Qbeast Visualiser (qviz) with multiblock files --------- Co-authored-by: Jorge Marín Co-authored-by: Jorge Marín <100561030+jorgeMarin1@users.noreply.github.com> --- utils/visualizer/Makefile | 8 - utils/visualizer/README.md | 22 +- .../_delta_log/00000000000000000000.json | 23 - utils/visualizer/poetry.lock | 701 +++++++++--------- utils/visualizer/pyproject.toml | 52 +- utils/visualizer/qviz/__init__.py | 2 +- utils/visualizer/qviz/block.py | 73 ++ utils/visualizer/qviz/content_loader.py | 338 +++------ utils/visualizer/qviz/cube.py | 139 +--- utils/visualizer/qviz/drawing_elements.py | 91 --- utils/visualizer/qviz/qviz.py | 88 ++- utils/visualizer/qviz/sampling_info.py | 54 ++ utils/visualizer/setup.py | 6 +- utils/visualizer/tests/block_test.py | 47 ++ utils/visualizer/tests/content_loader_test.py | 65 -- utils/visualizer/tests/cube_test.py | 109 ++- .../visualizer/tests/drawing_elements_test.py | 64 -- .../00000000000000000010.checkpoint.parquet | Bin 33435 -> 0 bytes .../_delta_log/00000000000000000010.json | 19 - .../_delta_log/_last_checkpoint | 1 - .../_delta_log/00000000000000000000.json | 23 - .../_delta_log/00000000000000000001.json | 23 - .../_delta_log/00000000000000000000.json | 29 +- .../00000000000000000001.checkpoint.parquet | Bin 20770 -> 0 bytes .../_delta_log/00000000000000000001.json | 36 +- .../_delta_log/00000000000000000002.json | 10 - .../test_table/_delta_log/_last_checkpoint | 1 - utils/visualizer/tests/sampling_info_test.py | 47 ++ 28 files changed, 853 insertions(+), 1218 deletions(-) delete mode 100644 utils/visualizer/Makefile delete mode 100644 utils/visualizer/docs/test_table/_delta_log/00000000000000000000.json create mode 100644 utils/visualizer/qviz/block.py delete mode 100644 utils/visualizer/qviz/drawing_elements.py create mode 100644 utils/visualizer/qviz/sampling_info.py create mode 100644 utils/visualizer/tests/block_test.py delete mode 100644 utils/visualizer/tests/content_loader_test.py delete mode 100644 utils/visualizer/tests/drawing_elements_test.py delete mode 100644 utils/visualizer/tests/resources/table_with_checkpoint/_delta_log/00000000000000000010.checkpoint.parquet delete mode 100644 utils/visualizer/tests/resources/table_with_checkpoint/_delta_log/00000000000000000010.json delete mode 100644 utils/visualizer/tests/resources/table_with_checkpoint/_delta_log/_last_checkpoint delete mode 100644 utils/visualizer/tests/resources/table_with_remove/_delta_log/00000000000000000000.json delete mode 100644 utils/visualizer/tests/resources/table_with_remove/_delta_log/00000000000000000001.json delete mode 100644 utils/visualizer/tests/resources/test_table/_delta_log/00000000000000000001.checkpoint.parquet delete mode 100644 utils/visualizer/tests/resources/test_table/_delta_log/00000000000000000002.json delete mode 100644 utils/visualizer/tests/resources/test_table/_delta_log/_last_checkpoint create mode 100644 utils/visualizer/tests/sampling_info_test.py diff --git a/utils/visualizer/Makefile b/utils/visualizer/Makefile deleted file mode 100644 index 26470b040..000000000 --- a/utils/visualizer/Makefile +++ /dev/null @@ -1,8 +0,0 @@ -install: - pip install --upgrade pip &&\ - pip install -r requirements.txt -e . - -test: - cd ./tests &&\ - python -m unittest discover -p "*_test.py" &&\ - cd .. diff --git a/utils/visualizer/README.md b/utils/visualizer/README.md index ae5d09b5d..ae2ded475 100644 --- a/utils/visualizer/README.md +++ b/utils/visualizer/README.md @@ -17,8 +17,7 @@ cd qbeast-spark/utils/visualizer # Pyhton version should be 3.12 python3 --version -# Install tool and required dependencies -brew install poetry +# Install required dependencies poetry install ``` @@ -28,7 +27,7 @@ Launch a `Flask` serve with the following command and open the link with a brows ```bash # Run the tool on the test table -poetry run qviz docs/test_table/ +poetry run qviz tests/resources/test_table/ # optionally, specify the index revision(defaulted to 1) # qviz --revision-id=2 @@ -39,17 +38,20 @@ poetry run qviz docs/test_table/ - Sampling details: when a valid value for sampling fraction is given, a set of sampling metrics are displayed. **Only the chosen revision is taken into account for computing sampling metrics.** ``` -Sampling Info: +Sampling Info: Disclaimer: - The displayed sampling metrics are valid only for single revision indexes(excluding revision 0): + The displayed sampling metrics are only for the chosen revisionId. + The values will be different if the table contains multiple revisions. - sample fraction: 0.3 - number of cubes read:8/20, 40.00% - number of rows: 41858/100000, 41.86% - sample size: 0.00141/0.00324GB, 43.49% + sample fraction: 0.02 + number of rows: 751130/8000000, 9.39% + sample size: 0.04273/2.09944GB, 2.04% ``` - To visualize any table, point to the folder in which the target `_delta_log/` is contained. - To visualize a remote table, download its `_delta_log/` and point to the folder location. ### Tests -- To run all tests: `make test` +- To run all tests: +```bash +python -m unittest discover -s tests -p "*_test.py" +``` diff --git a/utils/visualizer/docs/test_table/_delta_log/00000000000000000000.json b/utils/visualizer/docs/test_table/_delta_log/00000000000000000000.json deleted file mode 100644 index effc93131..000000000 --- a/utils/visualizer/docs/test_table/_delta_log/00000000000000000000.json +++ /dev/null @@ -1,23 +0,0 @@ -{"protocol":{"minReaderVersion":1,"minWriterVersion":2}} -{"metaData":{"id":"7445e94b-94fe-4333-a03d-7d0a48b190bb","format":{"provider":"parquet","options":{}},"schemaString":"{\"type\":\"struct\",\"fields\":[{\"name\":\"event_time\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"event_type\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"product_id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"category_id\",\"type\":\"long\",\"nullable\":true,\"metadata\":{}},{\"name\":\"category_code\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"brand\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}},{\"name\":\"price\",\"type\":\"double\",\"nullable\":true,\"metadata\":{}},{\"name\":\"user_id\",\"type\":\"integer\",\"nullable\":true,\"metadata\":{}},{\"name\":\"user_session\",\"type\":\"string\",\"nullable\":true,\"metadata\":{}}]}","partitionColumns":[],"configuration":{"qbeast.revision.0":"{\"revisionID\":0,\"timestamp\":1676304712186,\"tableID\":\"/tmp/test_table/\",\"desiredCubeSize\":10000,\"columnTransformers\":[{\"className\":\"io.qbeast.core.transform.EmptyTransformer\",\"columnName\":\"user_id\"},{\"className\":\"io.qbeast.core.transform.EmptyTransformer\",\"columnName\":\"price\"}],\"transformations\":[{\"className\":\"io.qbeast.core.transform.EmptyTransformation\"},{\"className\":\"io.qbeast.core.transform.EmptyTransformation\"}]}","qbeast.lastRevisionID":"1","qbeast.revision.1":"{\"revisionID\":1,\"timestamp\":1676304712187,\"tableID\":\"/tmp/test_table/\",\"desiredCubeSize\":10000,\"columnTransformers\":[{\"className\":\"io.qbeast.core.transform.LinearTransformer\",\"columnName\":\"user_id\",\"dataType\":\"IntegerDataType\"},{\"className\":\"io.qbeast.core.transform.LinearTransformer\",\"columnName\":\"price\",\"dataType\":\"DoubleDataType\"}],\"transformations\":[{\"className\":\"io.qbeast.core.transform.LinearTransformation\",\"minNumber\":315309190,\"maxNumber\":566280860,\"nullValue\":429138612,\"orderedDataType\":\"IntegerDataType\"},{\"className\":\"io.qbeast.core.transform.LinearTransformation\",\"minNumber\":0.0,\"maxNumber\":2574.07,\"nullValue\":207.43983426733197,\"orderedDataType\":\"DoubleDataType\"}]}"},"createdTime":1676304715044}} -{"add":{"path":"2e2318ff-d0e1-4a40-910a-94a5e71bf6fa.parquet","partitionValues":{},"size":383097,"modificationTime":1676304714892,"dataChange":true,"stats":"{\"numRecords\":10111,\"minValues\":{\"event_time\":\"2019-10-31 18:50:57 UTC\",\"event_type\":\"cart\",\"product_id\":1000978,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":0.0,\"user_id\":450820761,\"user_session\":\"000ffc1a-50f3-4834-9480-0d5db716\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:53 UTC\",\"event_type\":\"view\",\"product_id\":60500006,\"category_id\":2175419595093967522,\"category_code\":\"sport.trainer\",\"brand\":\"zubr\",\"price\":1283.73,\"user_id\":566280676,\"user_session\":\"ffe0f236-9226-43f1-877f-398a1ed9�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":3301,\"brand\":1554,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"g","revision":"1","elementCount":"10111","minWeight":"-1717927374","maxWeight":"-1269647486"}}} -{"add":{"path":"cd9a3b18-5960-4c12-9b63-c47848757c29.parquet","partitionValues":{},"size":107125,"modificationTime":1676304714943,"dataChange":true,"stats":"{\"numRecords\":3054,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1002929,\"category_id\":2053013552293216471,\"category_code\":\"accessories.bag\",\"brand\":\"a-case\",\"price\":0.0,\"user_id\":534923749,\"user_session\":\"00ad6792-dd49-4731-92f8-0d7f9103\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:44 UTC\",\"event_type\":\"view\",\"product_id\":60500002,\"category_id\":2173216765583032544,\"category_code\":\"stationery.cartrige\",\"brand\":\"zubr\",\"price\":160.71,\"user_id\":550542172,\"user_session\":\"ffd1e02b-397e-48c0-938a-d8830b6c�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":1367,\"brand\":503,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gggA","revision":"1","elementCount":"3054","minWeight":"525895739","maxWeight":"2147483647"}}} -{"add":{"path":"b94a35e0-2a47-402e-9ba9-b1a27e390f96.parquet","partitionValues":{},"size":356130,"modificationTime":1676304714971,"dataChange":true,"stats":"{\"numRecords\":10623,\"minValues\":{\"event_time\":\"2019-10-31 18:50:55 UTC\",\"event_type\":\"cart\",\"product_id\":1000978,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"a-case\",\"price\":0.0,\"user_id\":534923749,\"user_session\":\"001ff2ab-8518-4d5a-90d8-a70a0cd9\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:58 UTC\",\"event_type\":\"view\",\"product_id\":60500010,\"category_id\":2175419595093967522,\"category_code\":\"stationery.cartrige\",\"brand\":\"zubr\",\"price\":321.73,\"user_id\":566280860,\"user_session\":\"ffedb17a-e320-4330-a8b2-62b9c30b�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":3884,\"brand\":1723,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggg","revision":"1","elementCount":"10623","minWeight":"-767905298","maxWeight":"523854987"}}} -{"add":{"path":"cd8a12af-fc6b-40ac-b3b6-f4e3d13af361.parquet","partitionValues":{},"size":91555,"modificationTime":1676304714892,"dataChange":true,"stats":"{\"numRecords\":2441,\"minValues\":{\"event_time\":\"2019-10-31 18:50:58 UTC\",\"event_type\":\"cart\",\"product_id\":1002876,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":161.14,\"user_id\":503817471,\"user_session\":\"00368bf2-fb65-4e8e-b58d-6178ede6\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:33 UTC\",\"event_type\":\"view\",\"product_id\":58000000,\"category_id\":2175419595093967522,\"category_code\":\"sport.trainer\",\"brand\":\"zubr\",\"price\":321.73,\"user_id\":519220432,\"user_session\":\"fff23bea-4db5-4b68-8c31-a39f9f55�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":695,\"brand\":454,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggAQ","revision":"1","elementCount":"2441","minWeight":"499257283","maxWeight":"2147483647"}}} -{"add":{"path":"5ed8b240-643a-44e7-97e7-038197b83f67.parquet","partitionValues":{},"size":114287,"modificationTime":1676304714944,"dataChange":true,"stats":"{\"numRecords\":3359,\"minValues\":{\"event_time\":\"2019-10-31 18:50:58 UTC\",\"event_type\":\"cart\",\"product_id\":1002974,\"category_id\":2053013552293216471,\"category_code\":\"accessories.bag\",\"brand\":\"accord\",\"price\":0.0,\"user_id\":519231427,\"user_session\":\"000ffc1a-50f3-4834-9480-0d5db716\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:54 UTC\",\"event_type\":\"view\",\"product_id\":60000025,\"category_id\":2173216765583032544,\"category_code\":\"sport.bicycle\",\"brand\":\"zyxel\",\"price\":160.62,\"user_id\":534887800,\"user_session\":\"ffd78f86-7e10-45f3-ba36-40e715ea�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":1504,\"brand\":631,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggAg","revision":"1","elementCount":"3359","minWeight":"500197361","maxWeight":"2147483647"}}} -{"add":{"path":"7b8ef0fa-b86c-4883-b996-784efba893ef.parquet","partitionValues":{},"size":166425,"modificationTime":1676304714956,"dataChange":true,"stats":"{\"numRecords\":5142,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1002099,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":321.76,\"user_id\":506738777,\"user_session\":\"00368bf2-fb65-4e8e-b58d-6178ede6\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:58 UTC\",\"event_type\":\"view\",\"product_id\":58300014,\"category_id\":2172371436436455782,\"category_code\":\"sport.trainer\",\"brand\":\"zubr\",\"price\":643.49,\"user_id\":534829201,\"user_session\":\"ffa70846-f314-4e5e-a3b5-c87dc791�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":1070,\"brand\":709,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggQ","revision":"1","elementCount":"5142","minWeight":"-767916974","maxWeight":"2147483647"}}} -{"add":{"path":"5e29da2d-deb8-408d-abe6-1cb4abccb3fa.parquet","partitionValues":{},"size":201804,"modificationTime":1676304714971,"dataChange":true,"stats":"{\"numRecords\":5816,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1002482,\"category_id\":2053013552293216471,\"category_code\":\"accessories.bag\",\"brand\":\"a-case\",\"price\":0.0,\"user_id\":550668210,\"user_session\":\"000ba8ad-2b49-4488-a82e-2542d67c\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:35 UTC\",\"event_type\":\"view\",\"product_id\":60500009,\"category_id\":2175419595093967522,\"category_code\":\"sport.trainer\",\"brand\":\"zte\",\"price\":160.62,\"user_id\":566280416,\"user_session\":\"ffedb17a-e320-4330-a8b2-62b9c30b�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":2562,\"brand\":923,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gggg","revision":"1","elementCount":"5816","minWeight":"523965069","maxWeight":"2147483647"}}} -{"add":{"path":"79f4d3f4-8fde-46bd-b90d-e5b1e5099515.parquet","partitionValues":{},"size":243363,"modificationTime":1676304714892,"dataChange":true,"stats":"{\"numRecords\":8023,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1002525,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":643.52,\"user_id\":506208823,\"user_session\":\"001f144f-7e00-4aaf-9ce0-75733bf8\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:59 UTC\",\"event_type\":\"view\",\"product_id\":59700001,\"category_id\":2172371436436455782,\"category_code\":\"sport.trainer\",\"brand\":\"zubr\",\"price\":1287.03,\"user_id\":566280780,\"user_session\":\"ffe7d642-b13b-4afe-993b-3d3443fd�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":851,\"brand\":474,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gw","revision":"1","elementCount":"8023","minWeight":"-1269520359","maxWeight":"2147483647"}}} -{"add":{"path":"a93c1146-41f7-4db6-a1b6-6cbabbe669d5.parquet","partitionValues":{},"size":109924,"modificationTime":1676304714945,"dataChange":true,"stats":"{\"numRecords\":2995,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1000978,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":161.28,\"user_id\":550619123,\"user_session\":\"001311c1-357c-4383-bddb-f5c1306d\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:29 UTC\",\"event_type\":\"view\",\"product_id\":58000000,\"category_id\":2175419595093967522,\"category_code\":\"sport.trainer\",\"brand\":\"zapco\",\"price\":321.73,\"user_id\":566280399,\"user_session\":\"fff68383-3ea8-4be9-aa7f-234bb728�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":589,\"brand\":420,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gggw","revision":"1","elementCount":"2995","minWeight":"524101490","maxWeight":"2147483647"}}} -{"add":{"path":"5dda54ff-8150-40c9-9267-05d20c7b24f3.parquet","partitionValues":{},"size":163818,"modificationTime":1676304714961,"dataChange":true,"stats":"{\"numRecords\":4798,\"minValues\":{\"event_time\":\"2019-10-31 18:50:57 UTC\",\"event_type\":\"cart\",\"product_id\":1002098,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":321.76,\"user_id\":534933712,\"user_session\":\"00029da2-57bf-4f8d-bd76-26312207\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:55 UTC\",\"event_type\":\"view\",\"product_id\":60400009,\"category_id\":2175419595093967522,\"category_code\":\"sport.trainer\",\"brand\":\"zeppelin\",\"price\":643.49,\"user_id\":566280536,\"user_session\":\"ffae37f4-6675-42bc-ab82-e19b7706�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":959,\"brand\":466,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggw","revision":"1","elementCount":"4798","minWeight":"-766242267","maxWeight":"2147483647"}}} -{"add":{"path":"03be5ac8-7b1d-49aa-a3d5-7cb9199776ba.parquet","partitionValues":{},"size":56703,"modificationTime":1676304714971,"dataChange":true,"stats":"{\"numRecords\":1505,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1002367,\"category_id\":2053013552351936731,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":161.39,\"user_id\":519231066,\"user_session\":\"00a729c4-3ea6-4720-a412-52c07c4c\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:50 UTC\",\"event_type\":\"view\",\"product_id\":58000000,\"category_id\":2175419595093967522,\"category_code\":\"sport.trainer\",\"brand\":\"zanussi\",\"price\":321.73,\"user_id\":534829201,\"user_session\":\"ffd78f86-7e10-45f3-ba36-40e715ea�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":408,\"brand\":299,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggAw","revision":"1","elementCount":"1505","minWeight":"499915983","maxWeight":"2147483647"}}} -{"add":{"path":"fab891c3-a7e9-4542-ad32-28d869c29ed7.parquet","partitionValues":{},"size":57245,"modificationTime":1676304714979,"dataChange":true,"stats":"{\"numRecords\":1465,\"minValues\":{\"event_time\":\"2019-10-31 18:50:57 UTC\",\"event_type\":\"cart\",\"product_id\":1002876,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":160.88,\"user_id\":534920017,\"user_session\":\"0027f287-b8ef-4507-bbb9-31f6e323\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:53:18 UTC\",\"event_type\":\"view\",\"product_id\":58000000,\"category_id\":2172371436436455782,\"category_code\":\"sport.trainer\",\"brand\":\"yasin\",\"price\":321.73,\"user_id\":550581794,\"user_session\":\"ffdc4118-9bfd-e5ff-63a1-fe4eb3bd�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":342,\"brand\":261,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gggQ","revision":"1","elementCount":"1465","minWeight":"526791054","maxWeight":"2147483647"}}} -{"add":{"path":"dd9dcbb5-0161-40c4-9b52-2f37322bd79b.parquet","partitionValues":{},"size":9069,"modificationTime":1676304714892,"dataChange":true,"stats":"{\"numRecords\":145,\"minValues\":{\"event_time\":\"2019-10-31 18:51:19 UTC\",\"event_type\":\"view\",\"product_id\":1003374,\"category_id\":2053013552888807671,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":1.3,\"user_id\":450084266,\"user_session\":\"007d47ab-e482-4b82-8f83-1392ecdc\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:34 UTC\",\"event_type\":\"view\",\"product_id\":51800024,\"category_id\":2135658542386905834,\"category_code\":\"furniture.living_room.sofa\",\"brand\":\"xiaomi\",\"price\":630.62,\"user_id\":500774449,\"user_session\":\"ff9acf7d-b86e-40fd-be85-61f19f67�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":50,\"brand\":40,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gA","revision":"1","elementCount":"145","minWeight":"-1251363116","maxWeight":"2147483647"}}} -{"add":{"path":"dd5ef411-0d10-4ef0-94ad-6e83d9360b20.parquet","partitionValues":{},"size":377164,"modificationTime":1676304714944,"dataChange":true,"stats":"{\"numRecords\":9997,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1000978,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"abtoys\",\"price\":0.0,\"user_id\":315309190,\"user_session\":\"001f605b-7d09-4cdd-ab75-b9e3401a\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:58 UTC\",\"event_type\":\"view\",\"product_id\":60500008,\"category_id\":2173216765583032544,\"category_code\":\"sport.trainer\",\"brand\":\"zyxel\",\"price\":2574.04,\"user_id\":566280663,\"user_session\":\"fff68383-3ea8-4be9-aa7f-234bb728�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":3236,\"brand\":1577,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"","revision":"1","elementCount":"9997","minWeight":"-2147445030","maxWeight":"-1717986918"}}} -{"add":{"path":"1bb97a77-f31f-4e2d-ac30-8157a9a2706a.parquet","partitionValues":{},"size":367946,"modificationTime":1676304714960,"dataChange":true,"stats":"{\"numRecords\":9930,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1000978,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"a-case\",\"price\":0.0,\"user_id\":506738777,\"user_session\":\"000ffc1a-50f3-4834-9480-0d5db716\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:59 UTC\",\"event_type\":\"view\",\"product_id\":60500002,\"category_id\":2173216765583032544,\"category_code\":\"sport.trainer\",\"brand\":\"zubr\",\"price\":643.49,\"user_id\":566280697,\"user_session\":\"ffe7d642-b13b-4afe-993b-3d3443fd�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":3603,\"brand\":1667,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gg","revision":"1","elementCount":"9930","minWeight":"-1269646073","maxWeight":"-768067226"}}} -{"add":{"path":"e4dd71be-7e41-4be6-a171-bfff091451e4.parquet","partitionValues":{},"size":4100,"modificationTime":1676304714892,"dataChange":true,"stats":"{\"numRecords\":29,\"minValues\":{\"event_time\":\"2019-10-31 19:30:01 UTC\",\"event_type\":\"view\",\"product_id\":1004225,\"category_id\":2053013555631882655,\"category_code\":\"computers.desktop\",\"brand\":\"acer\",\"price\":669.23,\"user_id\":454621403,\"user_session\":\"226d23ba-1e3a-454e-8996-c0a7dedf\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:53:37 UTC\",\"event_type\":\"view\",\"product_id\":26300188,\"category_id\":2053013563584283495,\"category_code\":\"electronics.smartphone\",\"brand\":\"samsung\",\"price\":1133.36,\"user_id\":498494384,\"user_session\":\"b2346f88-706a-493c-8b8b-2e089b70�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":2,\"brand\":0,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"gQ","revision":"1","elementCount":"29","minWeight":"-1234528541","maxWeight":"2147483647"}}} -{"add":{"path":"fc1db6f9-3d50-43d2-bc35-8ff380959bcc.parquet","partitionValues":{},"size":4857,"modificationTime":1676304714942,"dataChange":true,"stats":"{\"numRecords\":45,\"minValues\":{\"event_time\":\"2019-10-31 19:01:19 UTC\",\"event_type\":\"view\",\"product_id\":1004856,\"category_id\":2053013553115300101,\"category_code\":\"apparel.shoes\",\"brand\":\"alis\",\"price\":4.09,\"user_id\":315309190,\"user_session\":\"07a6ad9c-b725-4411-8055-741ef0f6\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:25:50 UTC\",\"event_type\":\"view\",\"product_id\":43100015,\"category_id\":2172371118332051820,\"category_code\":\"kids.carriage\",\"brand\":\"wingoffly\",\"price\":1081.08,\"user_id\":437516891,\"user_session\":\"fd10dcb4-c26a-4ba4-9d72-3034a76d�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":23,\"brand\":14,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"A","revision":"1","elementCount":"45","minWeight":"-1670601101","maxWeight":"2147483647"}}} -{"add":{"path":"e109b91c-3aad-4dda-b818-7df716665641.parquet","partitionValues":{},"size":335212,"modificationTime":1676304714960,"dataChange":true,"stats":"{\"numRecords\":10547,\"minValues\":{\"event_time\":\"2019-10-31 18:50:58 UTC\",\"event_type\":\"cart\",\"product_id\":1000978,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"a-case\",\"price\":0.0,\"user_id\":506307553,\"user_session\":\"000ffc1a-50f3-4834-9480-0d5db716\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:50 UTC\",\"event_type\":\"view\",\"product_id\":60500002,\"category_id\":2173216765583032544,\"category_code\":\"sport.trainer\",\"brand\":\"zyxel\",\"price\":321.73,\"user_id\":534908832,\"user_session\":\"fff23bea-4db5-4b68-8c31-a39f9f55�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":4329,\"brand\":1975,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggA","revision":"1","elementCount":"10547","minWeight":"-767979740","maxWeight":"499137121"}}} -{"add":{"path":"ba3383aa-3d7e-4f0e-80ef-62557b01c068.parquet","partitionValues":{},"size":123310,"modificationTime":1676304714971,"dataChange":true,"stats":"{\"numRecords\":3578,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1004231,\"category_id\":2053013552259662037,\"category_code\":\"accessories.bag\",\"brand\":\"acer\",\"price\":1289.58,\"user_id\":498438023,\"user_session\":\"001f144f-7e00-4aaf-9ce0-75733bf8\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:49 UTC\",\"event_type\":\"view\",\"product_id\":59700006,\"category_id\":2172371436436455782,\"category_code\":\"sport.trainer\",\"brand\":\"zubr\",\"price\":2574.07,\"user_id\":566280291,\"user_session\":\"fff68383-3ea8-4be9-aa7f-234bb728�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":393,\"brand\":289,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"w","revision":"1","elementCount":"3578","minWeight":"-1716889048","maxWeight":"2147483647"}}} -{"add":{"path":"bcc9b4f5-11a8-446e-b24c-cedce722e85a.parquet","partitionValues":{},"size":205257,"modificationTime":1676304714982,"dataChange":true,"stats":"{\"numRecords\":6397,\"minValues\":{\"event_time\":\"2019-10-31 18:50:56 UTC\",\"event_type\":\"cart\",\"product_id\":1002974,\"category_id\":2053013552293216471,\"category_code\":\"accessories.bag\",\"brand\":\"a-case\",\"price\":0.0,\"user_id\":508212564,\"user_session\":\"0003c4d4-e489-4d6c-affc-c07515b0\"},\"maxValues\":{\"event_time\":\"2019-10-31 23:59:41 UTC\",\"event_type\":\"view\",\"product_id\":59000011,\"category_id\":2175419595093967522,\"category_code\":\"stationery.cartrige\",\"brand\":\"zubr\",\"price\":160.62,\"user_id\":519223634,\"user_session\":\"fffdee77-bb06-4541-aa64-99893a2d�\"},\"nullCount\":{\"event_time\":0,\"event_type\":0,\"product_id\":0,\"category_id\":0,\"category_code\":2912,\"brand\":1218,\"price\":0,\"user_id\":0,\"user_session\":0}}","tags":{"state":"FLOODED","cube":"ggAA","revision":"1","elementCount":"6397","minWeight":"499581100","maxWeight":"2147483647"}}} -{"commitInfo":{"timestamp":1676304715742,"operation":"WRITE","operationParameters":{"mode":"Overwrite"},"isolationLevel":"Serializable","isBlindAppend":false,"operationMetrics":{"numFiles":"20","numOutputRows":"100000","numOutputBytes":"3478391"},"engineInfo":"Apache-Spark/3.2.2 Delta-Lake/1.2.0","txnId":"ad110cec-0bd7-4784-a56d-0919a97dfc1f"}} diff --git a/utils/visualizer/poetry.lock b/utils/visualizer/poetry.lock index 446c6cd6b..f4e5ce81a 100644 --- a/utils/visualizer/poetry.lock +++ b/utils/visualizer/poetry.lock @@ -1,94 +1,14 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. [[package]] -name = "brotli" -version = "1.0.9" -description = "Python bindings for the Brotli compression library" +name = "blinker" +version = "1.8.2" +description = "Fast, simple object-to-object and broadcast signaling" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, - {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, - {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, - {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, - {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, - {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, - {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, - {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, - {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, - {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, - {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, - {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, - {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, - {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, - {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, - {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, - {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, - {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, - {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, - {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, - {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, - {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, - {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, - {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, - {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, - {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, - {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, - {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, - {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, - {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, - {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, - {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, - {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, - {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, - {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, - {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, - {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, - {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, - {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, - {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, - {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, - {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, - {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, - {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, - {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, ] [[package]] @@ -104,112 +24,127 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] name = "click" -version = "8.1.3" +version = "8.1.7" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" files = [ - {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, - {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] @@ -228,13 +163,13 @@ files = [ [[package]] name = "dash" -version = "2.17.1" +version = "2.18.1" description = "A Python framework for building reactive web-apps. Developed by Plotly." optional = false python-versions = ">=3.8" files = [ - {file = "dash-2.17.1-py3-none-any.whl", hash = "sha256:3eefc9ac67003f93a06bc3e500cae0a6787c48e6c81f6f61514239ae2da414e4"}, - {file = "dash-2.17.1.tar.gz", hash = "sha256:ee2d9c319de5dcc1314085710b72cd5fa63ff994d913bf72979b7130daeea28e"}, + {file = "dash-2.18.1-py3-none-any.whl", hash = "sha256:07c4513bb5f79a4b936847a0b49afc21dbd4b001ff77ea78d4d836043e211a07"}, + {file = "dash-2.18.1.tar.gz", hash = "sha256:ffdf89690d734f6851ef1cb344222826ffb11ad2214ab9172668bf8aadd75d12"}, ] [package.dependencies] @@ -272,18 +207,20 @@ files = [ [[package]] name = "dash-cytoscape" -version = "0.3.0" +version = "1.0.2" description = "A Component Library for Dash aimed at facilitating network visualization in Python, wrapped around Cytoscape.js" optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "dash_cytoscape-0.3.0-py3-none-any.whl", hash = "sha256:718dc1568b9e7bfe7f64376aa903c64a1a1fe6daed4e559b254456f18dd3135f"}, - {file = "dash_cytoscape-0.3.0.tar.gz", hash = "sha256:a71ad4fe095570b71d4ad7c0d29199e9780c2e6796173d3b25fccc2cc58c855f"}, + {file = "dash_cytoscape-1.0.2.tar.gz", hash = "sha256:a61019d2184d63a2b3b5c06d056d3b867a04223a674cc3c7cf900a561a9a59aa"}, ] [package.dependencies] dash = "*" +[package.extras] +leaflet = ["dash-leaflet (>=1.0.16rc3)"] + [[package]] name = "dash-html-components" version = "2.0.0" @@ -307,91 +244,107 @@ files = [ ] [[package]] -name = "flask" -version = "2.2.5" -description = "A simple framework for building complex web applications." +name = "deltalake" +version = "0.20.2" +description = "Native Delta Lake Python binding based on delta-rs with Pandas integration" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Flask-2.2.5-py3-none-any.whl", hash = "sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf"}, - {file = "Flask-2.2.5.tar.gz", hash = "sha256:edee9b0a7ff26621bd5a8c10ff484ae28737a2410d99b0bb9a6850c7fb977aa0"}, + {file = "deltalake-0.20.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:fe0623e0b938d05d83d50f2537d750f732f9ecfea0936a71b18a01cd732b70a1"}, + {file = "deltalake-0.20.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d50f6f3c1c07ab13f9ee37fb0291a52f5e0961da6b2be3dbbbd2d0c2eebd9077"}, + {file = "deltalake-0.20.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04f9cda5b67e3ec4db1524eebd0ef7aaee0387683e2e3ce34790a54295d3465"}, + {file = "deltalake-0.20.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37e69797219b4e79f37e064c0282520d9dda35dd11a449f7c9080626deb91db3"}, + {file = "deltalake-0.20.2-cp38-abi3-win_amd64.whl", hash = "sha256:a6742240ef72fb60503f9d1bbdba658a175005740bf5df1b51187273c0771a0c"}, + {file = "deltalake-0.20.2.tar.gz", hash = "sha256:e13bbcff69fb0948d1969ad2a03cc80a2dd052c8a762d9808d75316c9a9b1639"}, ] [package.dependencies] -click = ">=8.0" -itsdangerous = ">=2.0" -Jinja2 = ">=3.0" -Werkzeug = ">=2.2.2" +pyarrow = ">=16" [package.extras] -async = ["asgiref (>=3.2)"] -dotenv = ["python-dotenv"] +devel = ["azure-storage-blob (==12.20.0)", "mypy (==1.10.1)", "packaging (>=20)", "pytest", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-timeout", "ruff (==0.5.2)", "sphinx (<=4.5)", "sphinx-rtd-theme", "toml", "wheel"] +pandas = ["pandas"] +pyspark = ["delta-spark", "numpy (==1.22.2)", "pyspark"] [[package]] -name = "flask-compress" -version = "1.12" -description = "Compress responses in your Flask app with gzip, deflate or brotli." +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." optional = false -python-versions = "*" +python-versions = ">=3.8" files = [ - {file = "Flask-Compress-1.12.tar.gz", hash = "sha256:e2159499f39d618a4d56ba0484e7b58b57956b9a2c6d3510f095f5bb14b7afc5"}, - {file = "Flask_Compress-1.12-py3-none-any.whl", hash = "sha256:9f4e40211755e86f85e5eb7d414856ef1e8751912caa78d62853169400335f0c"}, + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, ] [package.dependencies] -brotli = "*" -flask = "*" +blinker = ">=1.6.2" +click = ">=8.1.3" +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] [[package]] name = "idna" -version = "3.8" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ - {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, - {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "importlib-metadata" -version = "4.11.4" +version = "8.5.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, - {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "itsdangerous" -version = "2.1.2" +version = "2.2.0" description = "Safely pass data to untrusted environments and back." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, - {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, ] [[package]] name = "jinja2" -version = "3.1.2" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, - {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -402,51 +355,72 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "2.1.1" +version = "3.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, - {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, - {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, - {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, - {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, - {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, ] [[package]] @@ -462,78 +436,90 @@ files = [ [[package]] name = "numpy" -version = "2.1.1" +version = "2.1.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" files = [ - {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, - {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, - {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, - {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, - {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, - {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, - {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, - {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, - {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, - {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, + {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, + {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, + {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, + {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, + {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, + {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, + {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, + {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, + {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "plotly" -version = "5.8.1" +version = "5.24.1" description = "An open-source, interactive data visualization library for Python" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "plotly-5.8.1-py2.py3-none-any.whl", hash = "sha256:f46424f191ea6656440a849c281685685c3154864bbf36bb1fcd0709cb59a496"}, - {file = "plotly-5.8.1.tar.gz", hash = "sha256:63b0d47d21ec285cf18c9b413182fcf9af6cba8faa52d47a43323f7f28a85646"}, + {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, + {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, ] [package.dependencies] +packaging = "*" tenacity = ">=6.2.0" [[package]] @@ -624,19 +610,23 @@ six = ">=1.7.0" [[package]] name = "setuptools" -version = "65.5.1" +version = "75.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "setuptools-65.5.1-py3-none-any.whl", hash = "sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31"}, - {file = "setuptools-65.5.1.tar.gz", hash = "sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"}, + {file = "setuptools-75.2.0-py3-none-any.whl", hash = "sha256:a7fcb66f68b4d9e8e66b42f9876150a3371558f98fa32222ffaa5bced76406f8"}, + {file = "setuptools-75.2.0.tar.gz", hash = "sha256:753bb6ebf1f465a1912e19ed1d41f403a79173a9acf66a42e7e6aec45c3c16ec"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.5.2)"] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.collections", "jaraco.functools", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.11.*)", "pytest-mypy"] [[package]] name = "six" @@ -651,17 +641,18 @@ files = [ [[package]] name = "tenacity" -version = "8.0.1" +version = "9.0.0" description = "Retry code until it succeeds" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "tenacity-8.0.1-py3-none-any.whl", hash = "sha256:f78f4ea81b0fabc06728c11dc2a8c01277bfc5181b321a4770471902e3eb844a"}, - {file = "tenacity-8.0.1.tar.gz", hash = "sha256:43242a20e3e73291a28bcbcacfd6e000b02d3857a9a9fff56b297a27afdc932f"}, + {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"}, + {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"}, ] [package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] [[package]] name = "typing-extensions" @@ -676,13 +667,13 @@ files = [ [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -693,37 +684,41 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "werkzeug" -version = "2.2.3" +version = "3.0.4" description = "The comprehensive WSGI web application library." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Werkzeug-2.2.3-py3-none-any.whl", hash = "sha256:56433961bc1f12533306c624f3be5e744389ac61d722175d543e1751285da612"}, - {file = "Werkzeug-2.2.3.tar.gz", hash = "sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe"}, + {file = "werkzeug-3.0.4-py3-none-any.whl", hash = "sha256:02c9eb92b7d6c06f31a782811505d2157837cea66aaede3e217c7c27c039476c"}, + {file = "werkzeug-3.0.4.tar.gz", hash = "sha256:34f2371506b250df4d4f84bfe7b0921e4762525762bbd936614909fe25cd7306"}, ] [package.dependencies] MarkupSafe = ">=2.1.1" [package.extras] -watchdog = ["watchdog"] +watchdog = ["watchdog (>=2.3)"] [[package]] name = "zipp" -version = "3.8.0" +version = "3.20.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, - {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, ] [package.extras] -docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"] -testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] [metadata] lock-version = "2.0" -python-versions = "^3.12" -content-hash = "e2ad675eba33fb78206ad756c009503a87708b9afd1adc8a4e90b9add46ca3ae" +python-versions = "^3.12.5" +content-hash = "71c161e38125d559ad583ef793036e2409334b957c3e83569304766346b79d05" diff --git a/utils/visualizer/pyproject.toml b/utils/visualizer/pyproject.toml index 1643d02f6..feb98b51d 100644 --- a/utils/visualizer/pyproject.toml +++ b/utils/visualizer/pyproject.toml @@ -1,30 +1,42 @@ [tool.poetry] name = "qviz" -version = "0.0.1" -description = "OTree Index Visualization dependencies" -authors = ["qbeast"] +version = "0.1.0" +description = "CI tool for OTree index visualization" +authors = ["Qbeast"] +readme = "README.md" [tool.poetry.dependencies] -python = "^3.12" -Brotli = "1.0.9" -click = "8.1.3" -dash = "2.17.1" +python = "^3.12.5" +blinker = "1.8.2" +certifi = "2024.8.30" +charset-normalizer = "3.4.0" +click = "8.1.7" +dash = "2.18.1" dash-core-components = "2.0.0" -dash-cytoscape = "0.3.0" dash-html-components = "2.0.0" dash-table = "5.0.0" -Flask = "2.2.5" -Flask-Compress= "1.12" -importlib-metadata = "4.11.4" -itsdangerous = "2.1.2" -Jinja2 = "3.1.2" -MarkupSafe = "2.1.1" -plotly = "5.8.1" -tenacity = "8.0.1" -Werkzeug = "2.2.3" -zipp = "3.8.0" -setuptools = "65.5.1" -pyarrow = "~17.0.0" +dash-cytoscape = "1.0.2" +deltalake = "0.20.2" +flask = "3.0.3" +idna = "3.10" +importlib-metadata = "8.5.0" +itsdangerous = "2.2.0" +jinja2 = "3.1.4" +markupsafe = "3.0.1" +nest-asyncio = "1.6.0" +numpy = "2.1.2" +packaging = "24.1" +plotly = "5.24.1" +pyarrow = "17.0.0" +requests = "2.32.3" +retrying = "1.3.4" +setuptools = "75.2.0" +six = "1.16.0" +tenacity = "9.0.0" +typing-extensions = "4.12.2" +urllib3 = "2.2.3" +werkzeug = "3.0.4" +zipp = "3.20.2" [tool.poetry.scripts] qviz = "qviz:show_tree" diff --git a/utils/visualizer/qviz/__init__.py b/utils/visualizer/qviz/__init__.py index 0b5dd4569..66c5846ea 100644 --- a/utils/visualizer/qviz/__init__.py +++ b/utils/visualizer/qviz/__init__.py @@ -1 +1 @@ -from qviz.qviz import show_tree +from qviz.qviz import show_tree as show_tree diff --git a/utils/visualizer/qviz/block.py b/utils/visualizer/qviz/block.py new file mode 100644 index 000000000..fa19735eb --- /dev/null +++ b/utils/visualizer/qviz/block.py @@ -0,0 +1,73 @@ +from __future__ import annotations +from dataclasses import dataclass + +OFFSET = -2147483648.0 # Scala Int.MinValue +RANGE = 2147483647.0 - OFFSET + + +@dataclass +class File: + path: str + bytes: int + num_rows: int + + @classmethod + def from_add_file_json(cls, add_file: dict) -> File: + _path = add_file["path"] + _bytes = add_file["size_bytes"] + _num_rows = add_file["num_records"] + return cls(_path, _bytes, _num_rows) + + def __hash__(self) -> int: + return hash(self.path) + + +class Block: + def __init__( + self, + cube_id: str, + element_count: int, + min_weight: int, + max_weight: int, + file: File, + ) -> None: + self.cube_id = cube_id + self.element_count = element_count + self.min_weight = self.normalize_weight(min_weight) + self.max_weight = self.normalize_weight(max_weight) + self.file = file + + @staticmethod + def normalize_weight(weight: int) -> float: + """ + Map Weight to NormalizedWeight + :param weight: Weight + :return: A Weight's corresponding NormalizedWeight + """ + fraction = (weight - OFFSET) / RANGE + # We make sure fraction is within [0, 1] + normalized = max(0.0, min(1.0, fraction)) + return float("{:.3f}".format(normalized)) + + @classmethod + def from_block_and_file(cls, block_json: dict, add_file_json: dict) -> Block: + cube_id = block_json["cubeId"] + element_count = block_json["elementCount"] + min_weight = block_json["minWeight"] + max_weight = block_json["maxWeight"] + file = File.from_add_file_json(add_file_json) + return cls( + cube_id=cube_id, + element_count=element_count, + min_weight=min_weight, + max_weight=max_weight, + file=file, + ) + + def is_sampled(self, fraction: float) -> bool: + """ + Determine if the cube is to be included in sampling for a given fraction + :param fraction: sampling fraction between 0 and 1 + :return: boolean determining if the cube is selected + """ + return self.min_weight <= fraction diff --git a/utils/visualizer/qviz/content_loader.py b/utils/visualizer/qviz/content_loader.py index a8ef5131a..e776955f7 100644 --- a/utils/visualizer/qviz/content_loader.py +++ b/utils/visualizer/qviz/content_loader.py @@ -1,251 +1,125 @@ import json -import os -import glob -from typing import Optional +from collections import defaultdict +from deltalake import DeltaTable -import pyarrow.parquet as pa +from qviz.block import Block +from qviz.cube import Cube +from qviz.sampling_info import SamplingInfoBuilder -def process_table_delta_log(table_path: str, revision_id: str) -> (list[dict], Optional[dict]): +def process_table(table_path: str, revision_id: int) -> tuple[dict, list[dict]]: """ - Extract valid AddFiles from table's _delta_log - :param table_path: path to qbeast table - :param revision_id: target RevisionID - :return: a list of AddFile dictionaries and optionally the metadata dictionary + Load the table and revision, and populate the tree with cubes. + The elements to be displayed are the cube nodes and edges. """ - assert revision_id != '0', "The processing of the staging revision is currently not supported." + delta_table = DeltaTable(table_path) + revision = load_revision(delta_table, revision_id) + cubes = load_revision_cubes(delta_table, revision) + populate_tree(cubes) + elements = get_nodes_and_edges(cubes) + return cubes, elements - log_path = os.path.join(table_path, "_delta_log") - last_checkpoint_file = glob.glob(os.path.join(log_path, "_last_checkpoint")) - - all_json_files = glob.glob(os.path.join(log_path, "*.json")) - assert len(all_json_files) != 0, "No json log file is found." - - if len(last_checkpoint_file) == 0: - result = extract_addFiles("", all_json_files, revision_id) - # Extract revision metadata - metadata = extract_revision_metadata("", all_json_files, revision_id) - else: - checkpoint_version = extract_checkpoint_version(last_checkpoint_file[0]) - - # example checkpoint file - # table_path/_delta_log/00000000000000000010.checkpoint.parquet - checkpoint_files = glob.glob(os.path.join(log_path, "*.checkpoint.parquet")) - for file in checkpoint_files: - # 00000000000000000010.checkpoint.parquet - file_name = file.split("/")[-1] - # 10 - version = int(file_name.split(".")[0]) - if version == checkpoint_version: - checkpoint_file = file - break - else: - raise Exception(f"No checkpoint file found with version {checkpoint_version}.") - - json_files = [] - # example json file: table_path/_delta_log/00000000000000000010.json - for file in all_json_files: - file_name = file.split("/")[-1] - version = int(file_name.split(".")[0]) - if version > checkpoint_version: - json_files.append(file) - - result = extract_addFiles(checkpoint_file, json_files, revision_id) - # Extract revision metadata - metadata = extract_revision_metadata(checkpoint_file, json_files, revision_id) - - if len(result) == 0: - raise Exception(f"No revision data available at revisionID={revision_id}") - - return result, metadata - - -def extract_checkpoint_version(last_checkpoint_file: str) -> int: +def load_revision(delta_table: DeltaTable, revision_id: int) -> (dict, int): """ - The _last_checkpoint file contains the last checkpoint version, - and the number of ActionFiles contained in the last parquet checkpoint file. - example _last_checkpoint file content - {"version":20,"size":202} - :param last_checkpoint_file: path to the last checkpoint file - :return: version of the last checkpoint - """ - with open(last_checkpoint_file, 'r') as f: - line = f.readline() - return json.loads(line)["version"] - - -def extract_addFiles(checkpoint_file: str, json_files: list[str], revision_id: str) -> list[dict]: - # extract add files from checkpoint file - add_files_from_checkpoint = addFiles_from_checkpoint_file(checkpoint_file, revision_id) - # extract add files from json files - (add_files_from_json, remove_files_from_json) = addFiles_from_json_log_files(json_files, revision_id) - # Apply Remove to AddFiles - return list(filter( - lambda add_file: add_file['path'] not in remove_files_from_json, - add_files_from_checkpoint + add_files_from_json) - ) - - -def addFiles_from_checkpoint_file(checkpoint_file: str, revision_id: str) -> list[dict]: - """ - Find all valid AddFiles from the checkpoint where their revisionID matches - the provided value. A AddFile is valid is no RemoveFile exists with the same - path. - example checkpoint entry: - { - 'txn': None, - 'add': {'path': '7d807c65-b3a7-4b74-8cee-9eafb1ded46a.parquet', - 'size': 123310, - ... - 'tags': [('state', 'FLOODED'), - ('cube', 'w'), - ...], - 'stats': '...'}, - 'remove': None, - 'metaData': None, - 'protocol': None} - :param checkpoint_file: path to the last checkpoint file - :param revision_id: revisionID of the index - :return: a list of AddFile - """ - if not checkpoint_file: - return [] - - # read parquet checkpoint file - log_entry_list = pa.read_table(checkpoint_file).to_pylist() - - add_files_dict = dict() - for entry in log_entry_list: - # AddFile entry - if entry['add']: - add_file = entry['add'] - tags = dict(add_file['tags']) - if tags['revision'] == revision_id: - path = add_file['path'] - add_file['tags'] = tags - add_files_dict[path] = add_file - for entry in log_entry_list: - # RemoveFile entry - if entry['remove']: - path = entry['remove']['path'] - add_files_dict.pop(path, None) - - return list(add_files_dict.values()) - - -def addFiles_from_json_log_files(json_files: list[str], revision_id: str) -> (list[dict], set[str]): - """ - Extract AddFiles and RemoveFiles from all json log files - :param json_files: list, a list of json log file paths - :param revision_id: target revisionID - :return: a list of AddFile and a set of RemoveFile paths - """ - add_files = list() - remove_paths = set() - for file in json_files: - (adds, removes) = load_single_log_file(file, revision_id) - add_files.extend(adds) - remove_paths = remove_paths.union(removes) - - return add_files, remove_paths - - -def load_single_log_file(file_path: str, revision_id: str) -> (list[dict], set[str]): - """ - Extract valid AddFiles from a single json log file. An AddFile is valid - if there's no RemoveFile with the same path. - example log file entries: - {"metaData":{"id":"c109cfb6-a770-4c02-b004-d44bbe91e981", ...}} - {"add":{"path":"b4542891-5b03-40cc-8ef1-293493e21814.parquet", ...}} - {"remove":{"path":"272bbd79-2bf0-444d-b15e-8e4326c9d281.parquet", ...}} - :param file_path: json log file path - :param revision_id: target RevisionID - :return: a list of AddFiles, and a set of RemoveFile paths - """ - with open(file_path, 'r') as f: - lines = f.readlines() - - add_files = list() - remove_paths = set() - for line in lines: - action_file = json.loads(line) - # AddFile entry - if 'add' in action_file: - add_file = action_file['add'] - _id = add_file['tags']['revision'] - if _id == revision_id: - # Add revision AddFile to result - add_files.append(add_file) - # RemoveFile entry - elif 'remove' in action_file: - remove_file = action_file['remove'] - remove_paths.add(remove_file['path']) - - return add_files, remove_paths - - -def extract_revision_metadata(checkpoint_file: str, json_files: list[str], revision_id: str) -> dict: - """ - Extract qbeast metadata configuration entry in the _delta_log/ that contains the target revision. - Example revision metadata: - {'revisionID': 1, - 'timestamp': 1675942792996, - 'tableID': '/tmp/test1/', - 'desiredCubeSize': 10000, - 'columnTransformers': ... - 'transformations': ... - } - :param checkpoint_file: checkpoint_file if there's any, otherwise the param is left empty: "" - :param json_files: list of json log files after the checkpoint - :param revision_id: target RevisionID - :return: qbeast metadata configuration for the target RevisionID + Load revision metadata from the given revision ID. """ + config = delta_table.metadata().configuration revision_key = f"qbeast.revision.{revision_id}" - # Try to extract the target revision metadata, if any, from the checkpoint file - metadata_configuration = extract_metadata_from_checkpoint(checkpoint_file, revision_key) - - return metadata_configuration or extract_metadata_from_json_files(json_files, revision_key) + try: + revision_str = config[revision_key] + revision = json.loads(revision_str) + return revision + except KeyError as e: + available_revisions = [ + k for k in config.keys() if k.startswith("qbeast.revision.") + ] + print( + f"No metadata found for the given RevisionID: {revision_id}. Available revisions: {available_revisions}" + ) + raise e + + +def load_revision_cubes(delta_table: DeltaTable, revision: dict) -> dict: + """ + Load cubes from the given revision. Each cube contains a list of blocks, and one file + can contain multiple blocks. + """ + revision_id_str = str(revision["revisionID"]) + dimension_count = len(revision["columnTransformers"]) + symbol_count = (dimension_count + 5) // 6 + all_cubes = dict() + add_files = delta_table.get_add_actions(True).to_pylist() + for add_file in add_files: + if add_file.get("tags.revision", "-1") != revision_id_str: + continue + blocks = json.loads(add_file["tags.blocks"]) + for block_dict in blocks: + cube_id = block_dict["cubeId"] + if cube_id not in all_cubes: + depth = len(cube_id) // symbol_count + cube = Cube(cube_id, depth=depth) + all_cubes[cube_id] = cube + cube = all_cubes[cube_id] + block = Block.from_block_and_file(block_dict, add_file) + cube.add(block) + return all_cubes + + +def populate_tree(all_cubes: dict) -> None: + """ + Establish parent-child relationships between cubes + """ + max_level = 0 + level_cubes = defaultdict(list) + for cube in all_cubes.values(): + level_cubes[cube.depth].append(cube) + max_level = max(max_level, cube.depth) + for level in range(max_level): + for cube in level_cubes[level]: + for child in level_cubes[level + 1]: + child.link(cube) + return None -def extract_metadata_from_checkpoint(checkpoint_file: str, revision_key: str) -> Optional[dict]: +def get_nodes_and_edges(all_cubes: dict, fraction: float = -1.0) -> list[dict]: """ - Extract target revision metadata from the checkpoint_file - :param checkpoint_file: path to the latest checkpoint_file - :param revision_key: Configuration entry for the target RevisionID. e.g. qbeast.revision.1 - :return: optional metadata configuration for the target RevisionID + Create nodes and edges for the tree. If fraction is provided, highlight sampled cubes and print sampling details. """ - if not checkpoint_file: - return None - - log_entry_list = pa.read_table(checkpoint_file).to_pylist() - for record in log_entry_list: - if record['metaData']: - configuration_dict = dict(record['metaData']['configuration']) - if revision_key in configuration_dict: - return json.loads(configuration_dict[revision_key]) - return None + nodes = [] + edges = [] + sampling_info_builder = SamplingInfoBuilder(fraction) + for cube in all_cubes.values(): + node, connections = get_node_and_edges_from_cube(cube, fraction) + nodes.append(node) + edges.extend(connections) + if 0.0 < fraction <= 1.0: + sampling_info_builder.update(cube) + if 0.0 < fraction <= 1.0: + print(sampling_info_builder.result()) + return nodes + edges -def extract_metadata_from_json_files(json_files: list[str], revision_key: str) -> Optional[dict]: +def get_node_and_edges_from_cube(cube: Cube, fraction: float) -> (dict, list[dict]): """ - Extract target revision metadata from json log files. Only the first line from the file is read. - Example json log file content with only three lines: - {'metadata': ...} - {'add': ...} - {'commitInfo': ...} - :param json_files: list of json log files - :param revision_key: Configuration entry for the target RevisionID. e.g. qbeast.revision.1 - :return: optional metadata configuration for the target RevisionID + Create a node and edges for a given cube. If fraction is provided, highlight sampled cubes. """ - for file in json_files: - with open(file, 'r') as f: - lines = f.readlines() - - for line in lines: - log_in_json = json.loads(line) - if 'metaData' in log_in_json: - configuration = log_in_json['metaData']['configuration'] - if revision_key in configuration: - return json.loads(configuration[revision_key]) - return None + selected = cube.is_sampled(fraction) + name = cube.cube_id or "root" + label = (name + " " if name == "root" else "") + str(cube.max_weight) + node = { + "data": {"id": name, "label": label}, + "selected": selected, + "classes": "sampled" if selected else "", + } + edges = [] + for child in cube.children: + selected_child = child.is_sampled(fraction) + edges.append( + { + "data": {"source": name, "target": child.cube_id}, + "selected": selected_child, + "classes": "sampled" if selected_child else "", + } + ) + return node, edges diff --git a/utils/visualizer/qviz/cube.py b/utils/visualizer/qviz/cube.py index ae9751f9e..b4fd8d469 100644 --- a/utils/visualizer/qviz/cube.py +++ b/utils/visualizer/qviz/cube.py @@ -1,126 +1,53 @@ from __future__ import annotations -OFFSET = -2147483648.0 # Scala Int.MinValue -RANGE = 2147483647.0 - OFFSET +from qviz.block import Block class Cube: - def __init__(self, cube_string: str, max_weight: int, element_count: int, size: int, depth: int): - self.cube_string = cube_string - self.max_weight = normalize_weight(max_weight) - self.element_count = element_count + def __init__( + self, + cube_id: str, + depth: int, + ): + self.cube_id = cube_id self.depth = depth - self.size = size + + self.max_weight = float("inf") + self.min_weight = float("inf") + self.element_count = 0 self.children = [] + self.blocks = [] self.parent = None + def add(self, block: Block) -> None: + self.blocks.append(block) + self.max_weight = min(self.max_weight, block.max_weight) + self.min_weight = min(self.min_weight, block.min_weight) + self.element_count += block.element_count + + def is_sampled(self, fraction: float) -> bool: + return any(block.is_sampled(fraction) for block in self.blocks) + + @property + def size(self) -> int: + return sum({b.file.path: b.file.bytes for b in self.blocks}.values()) + def link(self, that: Cube) -> None: """ Establish parent-child cube relationship for two cubes if appropriate :param that: potential parent cube :return: None """ - if self.is_child_of(that): + if self._is_child_of(that): self.parent = that that.children.append(self) - def is_child_of(self, that: Cube) -> bool: - return (self.is_not_root() and - that.depth + 1 == self.depth and - self.cube_string.startswith(that.cube_string)) - - def is_not_root(self) -> bool: - return bool(self.cube_string) - - def get_elements_for_sampling(self, fraction: float) -> (dict, list[dict]): - """ - Return cube and edges for drawing if the cube or any of its children are selected - for the given sampling fraction. - :param fraction: sampling fraction between [0, 1] - :return: styled node and edges for drawing depending on if they are selected for - sampling - """ - selected = self.is_sampled(fraction) - name = self.cube_string or 'root' - label = (name + " " if name == 'root' else "") + str(self.max_weight) - node = { - 'data': {'id': name, 'label': label}, - 'selected': selected, - 'classes': "sampled" if selected else "" - } - - edges = [] - for child in self.children: - selected_child = child.is_sampled(fraction) - edges.append( - { - 'data': {'source': name, 'target': child.cube_string}, - 'selected': selected_child, - 'classes': "sampled" if selected_child else "" - } - ) - - return node, edges - - def is_sampled(self, fraction: float) -> bool: - """ - Determine if the cube is to be included in sampling for a given fraction - :param fraction: sampling fraction between 0 and 1 - :return: boolean determining if the cube is selected - """ - return (fraction > 0 and - (self.parent is None or - self.parent.max_weight < fraction)) - - def __repr__(self) -> str: - return f"Cube: {self.cube_string}, count: {self.element_count}, maxWeight: {self.max_weight}" - - -class SamplingInfo: - def __init__(self, f: float) -> None: - self.fraction = f - self.total_cubes = 0 - self.sampled_cubes = 0 - self.total_rows = 0 - self.sampled_rows = 0 - self.total_size = 0 - self.sampled_size = 0 - - def update(self, cube: Cube, is_sampled: bool) -> None: - self.total_cubes += 1 - self.total_rows += cube.element_count - self.total_size += cube.size / (1024 * 1024) - - if is_sampled: - self.sampled_cubes += 1 - self.sampled_rows += cube.element_count - self.sampled_size += cube.size / (1024 * 1024) - - def __repr__(self) -> str: - disclaimer = """Disclaimer: - \tThe displayed sampling metrics are valid only for single revision indexes(excluding revision 0):""" - file_count_percentage = self.sampled_cubes / self.total_cubes * 100 - row_count_percentage = self.sampled_rows / self.total_rows * 100 - size_count_percentage = self.sampled_size / self.total_size * 100 - return """Sampling Info:\ - \n\t{} - \n\tsample fraction: {}\ - \n\tnumber of cubes read:{}/{}, {:.2f}%\ - \n\tnumber of rows: {}/{}, {:.2f}%\ - \n\tsample size: {:.5f}/{:.5f}GB, {:.2f}%""".format( - disclaimer, - self.fraction, - self.sampled_cubes, self.total_cubes, file_count_percentage, - self.sampled_rows, self.total_rows, row_count_percentage, - self.sampled_size / 1024, self.total_size / 1024, size_count_percentage + def _is_child_of(self, that: Cube) -> bool: + return ( + self.depth > 0 + and self.depth == that.depth + 1 + and self.cube_id.startswith(that.cube_id) ) - -def normalize_weight(weight: int) -> float: - """ - Map Weight to NormalizedWeight - :param weight: Weight - :return: weight's corresponding NormalizedWeight - """ - fraction = (weight - OFFSET) / RANGE - return float("{:.3f}".format(fraction)) + def __repr__(self) -> str: + return f"Cube: {self.cube_id}, count: {self.element_count}, maxWeight: {self.max_weight}" diff --git a/utils/visualizer/qviz/drawing_elements.py b/utils/visualizer/qviz/drawing_elements.py deleted file mode 100644 index 6bf623750..000000000 --- a/utils/visualizer/qviz/drawing_elements.py +++ /dev/null @@ -1,91 +0,0 @@ -from collections import defaultdict -from qviz.cube import Cube, SamplingInfo - - -def process_add_files(add_files: list[dict], metadata: dict) -> list[Cube]: - """ - Convert AddFiles to Cubes - :param add_files: a list of AddFile dictionaries - :param metadata: metadata configuration for the target revision - :return: a list of all cube from the given revision - """ - if metadata: - dimension_count = len(metadata['columnTransformers']) - symbol_count = (dimension_count + 5) // 6 - else: - print(f"No metadata found for the given RevisionID.") - symbol_count = float('inf') - for add_file in add_files: - cube_encoding_size = len(add_file['tags']['cube']) - if 0 < cube_encoding_size < symbol_count: - symbol_count = cube_encoding_size - - # Group cube blocks by Cube string - cube_blocks = defaultdict(list) - for add_file in add_files: - cube_string = add_file['tags']['cube'] - cube_blocks[cube_string].append(add_file) - - cubes = [] - for blocks in cube_blocks.values(): - cube_string = blocks[0]['tags']['cube'] - depth = len(cube_string) // symbol_count - - size = 0 - max_weight = float("inf") - element_count = 0 - for add_file in blocks: - tags = add_file['tags'] - - max_weight = min(max_weight, int(tags['maxWeight'])) - element_count += int(tags['elementCount']) - size += int(add_file['size']) - - cubes.append(Cube(cube_string, max_weight, element_count, size, depth)) - - return cubes - - -def populate_tree(cubes: list[Cube]) -> Cube: - """ - Populate tree by iterating cubes from top down and creating parent-child references - :param cubes: list of Cube - :return: root cube with reference to child cubes - """ - max_level = 0 - level_cubes = defaultdict(list) - for cube in cubes: - level_cubes[cube.depth].append(cube) - max_level = max(max_level, cube.depth) - - for level in range(max_level): - for cube in level_cubes[level]: - for child in level_cubes[level + 1]: - child.link(cube) - - root = level_cubes[0][0] - return root - - -def get_nodes_and_edges(cubes: list[Cube], fraction: float = -1.0) -> list[dict]: - """ - Compute nodes and edges to draw according to a given sampling fraction, - :param cubes: list of populated cubes - :param fraction: sampling fraction from 0 to 1 - :return: nodes and edges to draw - """ - nodes = [] - edges = [] - - sampling_info = SamplingInfo(fraction) - for cube in cubes: - node, connections = cube.get_elements_for_sampling(fraction) - nodes.append(node) - edges.extend(connections) - - sampling_info.update(cube, node['selected']) - - if fraction > 0: - print(sampling_info) - - return nodes + edges diff --git a/utils/visualizer/qviz/qviz.py b/utils/visualizer/qviz/qviz.py index 91318994e..04169fa27 100644 --- a/utils/visualizer/qviz/qviz.py +++ b/utils/visualizer/qviz/qviz.py @@ -3,9 +3,7 @@ import dash_cytoscape as cyto import click -from qviz.content_loader import process_table_delta_log -from qviz.drawing_elements import process_add_files, populate_tree, get_nodes_and_edges - +from qviz.content_loader import process_table, get_nodes_and_edges cyto.load_extra_layouts() LAYOUT_NAME = "dagre" @@ -29,53 +27,51 @@ def show_tree(path: str, revision_id: int) -> None: index revision, so if there are more than one revision, the results will be inaccurate. """ - assert revision_id > 0, f"Invalid value for revision_id: {revision_id}, it must be > 0." + assert ( + revision_id > 0 + ), f"Invalid value for revision_id: {revision_id}, it must be > 0." - # Gather revision AddFiles from the table _delta_log/ - add_files, metadata = process_table_delta_log(path, str(revision_id)) - # Process build Cubes from AddFiles - cubes = process_add_files(add_files, metadata) - # Populate the index by creating parent-child references - _ = populate_tree(cubes) - # Gather drawing elements from populated cubes - elements = get_nodes_and_edges(cubes) + cubes, elements = process_table(path, revision_id) # Callback that modifies the drawing elements - @app.callback(Output('cyto-space', 'elements'), Input('fraction', 'value')) - def update_fraction_edges(fraction): - if fraction is None or fraction <= 0: - return elements - return get_nodes_and_edges(cubes, fraction) + @app.callback(Output("cyto-space", "elements"), Input("fraction", "value")) + def update_fraction_edges(fraction: float) -> list[dict]: + if fraction is not None and 0.0 < fraction <= 1.0: + return get_nodes_and_edges(cubes, fraction) + return elements - app.layout = html.Div([ - html.P("OTree Index"), - html.Div([ + app.layout = html.Div( + [ + html.P("OTree Index"), html.Div( - style={'width': '50%', 'display': 'inline'}, - children=['Sampling Fraction:', dcc.Input(id='fraction', type='number')] - ) - ]), - cyto.Cytoscape( - id='cyto-space', - elements=elements, - layout={'name': LAYOUT_NAME, 'roots': '#root'}, - style={'width': '100%', 'height': '1000px'}, - stylesheet=[ - # Display cube max_weight for reach node - {'selector': 'nodes', - 'style': { - 'label': 'data(label)' - } - }, - # Highlight sampled nodes and edges - {'selector': '.sampled', - 'style': { - 'background-color': 'blue', - 'line-color': 'blue' - } - } - ] - ) - ]) + [ + html.Div( + style={"width": "50%", "display": "inline"}, + children=[ + "Sampling Fraction:", + dcc.Input( + id="fraction", type="number", step=0.01, value=0.02 + ), + ], + ) + ] + ), + cyto.Cytoscape( + id="cyto-space", + elements=elements, + layout={"name": LAYOUT_NAME, "roots": "#root"}, + style={"width": "100%", "height": "1000px"}, + stylesheet=[ + # Display cube max_weight for reach node + {"selector": "nodes", "style": {"label": "data(label)"}}, + # Highlight sampled nodes and edges + { + "selector": ".sampled", + "style": {"background-color": "blue", "line-color": "blue"}, + }, + ], + ), + ] + ) app.run_server(debug=True) diff --git a/utils/visualizer/qviz/sampling_info.py b/utils/visualizer/qviz/sampling_info.py new file mode 100644 index 000000000..d6a455afb --- /dev/null +++ b/utils/visualizer/qviz/sampling_info.py @@ -0,0 +1,54 @@ +from qviz.block import File +from qviz.cube import Cube +from dataclasses import dataclass + + +@dataclass +class SamplingInfo: + fraction: float + total_rows: int + total_bytes: int + sampled_rows: int + sampled_bytes: int + + def __repr__(self) -> str: + disclaimer = """Disclaimer: + \tThe displayed sampling metrics are only for the chosen revisionId. + \tThe values will be different if the table contains multiple revisions.""" + num_rows_percentage = ( + self.sampled_rows / self.total_rows * 100 if (self.total_rows > 0) else -1 + ) + sampled_bytes_percentage = ( + self.sampled_bytes / self.total_bytes * 100 if (self.total_rows > 0) else -1 + ) + return f"""Sampling Info:\ + \n\t{disclaimer} + \n\tsample fraction: {self.fraction}\ + \n\tsampled rows: {self.sampled_rows}/{self.total_rows}, {num_rows_percentage:.2f}%\ + \n\tsampled size: {self.sampled_bytes / 1e9:.5f}/{self.total_bytes / 1e9:.5f}GB, {sampled_bytes_percentage:.2f}%""" + + +class SamplingInfoBuilder: + def __init__(self, f: float) -> None: + self.fraction: float = f + self.sampled_files: set[File] = set() + self.all_files: set[File] = set() + + def update(self, cube: Cube) -> None: + for block in cube.blocks: + self.all_files.add(block.file) + if block.is_sampled(self.fraction): + self.sampled_files.add(block.file) + + def result(self) -> SamplingInfo: + total_rows = sum(f.num_rows for f in self.all_files) + total_bytes = sum(f.bytes for f in self.all_files) + sampled_rows = sum(f.num_rows for f in self.sampled_files) + sampled_bytes = sum(f.bytes for f in self.sampled_files) + return SamplingInfo( + self.fraction, + total_rows, + total_bytes, + sampled_rows, + sampled_bytes, + ) diff --git a/utils/visualizer/setup.py b/utils/visualizer/setup.py index faf733396..8d805b978 100644 --- a/utils/visualizer/setup.py +++ b/utils/visualizer/setup.py @@ -7,9 +7,5 @@ author="Qbeast", description="CI tool for OTree index visualization", packages=setuptools.find_packages(), - entry_points={ - "console_scripts": [ - "qviz = qviz:show_tree" - ] - } + entry_points={"console_scripts": ["qviz = qviz:show_tree"]}, ) diff --git a/utils/visualizer/tests/block_test.py b/utils/visualizer/tests/block_test.py new file mode 100644 index 000000000..1cc9b7a48 --- /dev/null +++ b/utils/visualizer/tests/block_test.py @@ -0,0 +1,47 @@ +from unittest import TestCase + +from qviz.block import Block, File + + +class BlockTest(TestCase): + def setUp(self): + self.SCALA_MIN = -2147483648 # maps to 0.0 in normalize_weight + self.SCALA_MAX = 2147483647 # maps to 1.0 in normalize_weight + self.block = Block( + cube_id="A", + element_count=10, + min_weight=-1073741824, # maps to 0.25 in normalize_weight + max_weight=1073741823, # maps to 0.75 in normalize_weight + file=File(path="test_path", bytes=1024, num_rows=100), + ) + + def test_from_block_and_file(self): + block_json = { + "cubeId": "", + "elementCount": 10, + "minWeight": self.SCALA_MIN, + "maxWeight": self.SCALA_MAX, + } + add_file_json = {"path": "file_1", "size_bytes": 1024, "num_records": 100} + block = Block.from_block_and_file(block_json, add_file_json) + + self.assertEqual(block.cube_id, "") + self.assertEqual(block.element_count, 10) + self.assertEqual(block.min_weight, 0.0) + self.assertEqual(block.max_weight, 1.0) + self.assertEqual(block.file.path, "file_1") + self.assertEqual(block.file.bytes, 1024) + self.assertEqual(block.file.num_rows, 100) + + def test_normalize_weight(self): + self.assertEqual(Block.normalize_weight(self.SCALA_MIN), 0.0) + self.assertEqual(Block.normalize_weight(self.SCALA_MAX), 1.0) + self.assertEqual(Block.normalize_weight(0), 0.5) + self.assertEqual(Block.normalize_weight(-1073741824), 0.25) + self.assertEqual(Block.normalize_weight(1073741823), 0.75) + + def test_is_sampled(self): + self.assertFalse(self.block.is_sampled(0.24)) + self.assertTrue(self.block.is_sampled(0.25)) + self.assertTrue(self.block.is_sampled(0.5)) + self.assertTrue(self.block.is_sampled(0.9)) diff --git a/utils/visualizer/tests/content_loader_test.py b/utils/visualizer/tests/content_loader_test.py deleted file mode 100644 index 2db148b8d..000000000 --- a/utils/visualizer/tests/content_loader_test.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import unittest - -from qviz.content_loader import ( - extract_metadata_from_json_files, - extract_metadata_from_checkpoint, - addFiles_from_checkpoint_file, - addFiles_from_json_log_files, - extract_checkpoint_version, - process_table_delta_log -) - - -class TestContentLoader(unittest.TestCase): - def setUp(self) -> None: - root = os.getcwd() - self.table_path = os.path.join(root, "resources", "test_table") - log_path = os.path.join(self.table_path, "_delta_log") - # remove_log = os.path.join(root, "resources/table_with_remove", "_delta_log") - - self.first_log_file_path = os.path.join(log_path, "00000000000000000000.json") - self.checkpoint_file_path = os.path.join(log_path, "00000000000000000001.checkpoint.parquet") - self.json_log_files_with_remove = [ - os.path.join(log_path, "00000000000000000000.json"), - os.path.join(log_path, "00000000000000000001.json") - ] - self.last_checkpoint = os.path.join(log_path, "_last_checkpoint") - - def check_empty_revision(self, transformers: list[str]) -> None: - self.assertEqual(len(transformers), 2) - self.assertEqual(len(set(transformers)), 1) - self.assertEqual(set(transformers).pop(), 'io.qbeast.core.transform.EmptyTransformer') - - def test_metadata_extraction_from_json_files(self): - metadata = extract_metadata_from_json_files([self.first_log_file_path], "qbeast.revision.0") - transformers = list(map(lambda t: t['className'], metadata['columnTransformers'])) - self.check_empty_revision(transformers) - - def test_metadata_extraction_from_checkpoint_file(self): - metadata = extract_metadata_from_checkpoint(self.checkpoint_file_path, "qbeast.revision.0") - transformers = list(map(lambda t: t['className'], metadata['columnTransformers'])) - self.check_empty_revision(transformers) - - def test_version_extraction(self): - version = extract_checkpoint_version(self.last_checkpoint) - self.assertTrue(version == 1) - - def test_addFile_extraction_from_checkpoint_file(self): - add_files = addFiles_from_checkpoint_file(self.checkpoint_file_path, "1") - - def is_valid_add_file(add_file: dict) -> bool: - return 'path' in add_file and type(add_file['tags']) == dict - - self.assertTrue(all(is_valid_add_file(add_file) for add_file in add_files)) - - def test_addFile_extraction_from_json_log_files(self): - (add_files, remove_paths) = addFiles_from_json_log_files(self.json_log_files_with_remove, '1') - self.assertTrue(len(add_files) > len(remove_paths)) - - valid_add_files = list(filter(lambda add_file: add_file['path'] not in remove_paths, add_files)) - self.assertTrue(len(valid_add_files) == 8) - - def test_log_processing(self): - (add_files, _) = process_table_delta_log(self.table_path, '1') - self.assertTrue(all(['tags' in a for a in add_files])) diff --git a/utils/visualizer/tests/cube_test.py b/utils/visualizer/tests/cube_test.py index 22750465f..82c733255 100644 --- a/utils/visualizer/tests/cube_test.py +++ b/utils/visualizer/tests/cube_test.py @@ -1,72 +1,51 @@ import unittest -from qviz.cube import Cube, SamplingInfo +from qviz.block import Block, File +from qviz.cube import Cube -class TestCube(unittest.TestCase): - def test_is_not_root(self): - root = Cube("", 1, 1, 1, 0) - cube_level_1 = Cube("A", 1, 1, 1, 1) - - self.assertFalse(root.is_not_root()) - self.assertTrue(cube_level_1.is_not_root()) - - def test_link(self): - root = Cube("", 1, 1, 1, 0) - cube_level_1 = Cube("A", 1, 1, 1, 1) - cube_level_2 = Cube("AA", 1, 1, 1, 2) - - cube_level_1.link(root) - self.assertTrue(cube_level_1.parent is root) - self.assertTrue(root.children[0] is cube_level_1) - cube_level_2.link(root) - self.assertTrue(cube_level_2.parent is not root) - self.assertTrue(len(root.children) == 1) +class TestCube(unittest.TestCase): + def setUp(self): + self.file = File(path="test_path", bytes=1000, num_rows=10) + self.block = Block( + cube_id="A", + element_count=5, + min_weight=-1073741824, # maps to 0.25 in normalize_weight + max_weight=1073741823, # maps to 0.75 in normalize_weight + file=self.file, + ) + self.cube = Cube(cube_id="A", depth=1) + + def test_add_block(self): + self.cube.add(self.block) + self.assertEqual(len(self.cube.blocks), 1) + self.assertEqual(self.cube.element_count, 5) + self.assertEqual(self.cube.size, 1000) + self.assertEqual(self.cube.min_weight, 0.25) + self.assertEqual(self.cube.max_weight, 0.75) + + # Add another block + another_block = Block( + cube_id="A", + element_count=10, + min_weight=-2147483648, # maps to 0.0 in normalize_weight + max_weight=0, # maps to 1.0 in normalize_weight + file=File(path="another_test_path", bytes=2000, num_rows=20), + ) + self.cube.add(another_block) + self.assertEqual(len(self.cube.blocks), 2) + self.assertEqual(self.cube.element_count, 15) + self.assertEqual(self.cube.size, 3000) + self.assertEqual(self.cube.min_weight, 0.0) + self.assertEqual(self.cube.max_weight, 0.5) def test_is_sampled(self): - root = Cube("", -1269647486, 100, 100, 0) # max weight: 0.204 - cube = Cube("A", 100, 100, 100, 1) # max weight: 0.5 - cube.link(root) - - self.assertTrue(root.is_sampled(0.3)) - self.assertTrue(cube.is_sampled(0.3)) - - self.assertTrue(root.is_sampled(0.1)) - self.assertFalse(cube.is_sampled(0.1)) - - def test_get_elements_for_sampling(self): - root = Cube("", -1269647486, 100, 100, 0) # max weight: 0.204 - cube = Cube("A", 10000000000, 5, 5, 1) # max weight 2.828 - cube.link(root) + self.cube.add(self.block) + self.assertFalse(self.cube.is_sampled(0.1)) + self.assertTrue(self.cube.is_sampled(0.3)) - node, edges = root.get_elements_for_sampling(0.15) - self.assertTrue(node['data']['id'] == 'root') - self.assertTrue(node['selected']) - self.assertTrue(edges[0]['data']['source'] == 'root') - self.assertTrue(edges[0]['data']['target'] == 'A') - self.assertFalse(edges[0]['selected']) - - -class TestSamplingInfo(unittest.TestCase): - def test_update(self): - root = Cube("", -1269647486, 100, 5, 0) # max weight: 0.204 - cube_level_1 = Cube("A", 100, 100, 5, 1) # max weight: 0.5 - cube_level_2 = Cube("A", 10000000000, 50, 1, 2) # max weight 2.828 - - cube_level_1.link(root) - cube_level_2.link(cube_level_1) - - f = 0.3 - sampling_info = SamplingInfo(f) - sampling_info.update(root, root.is_sampled(f)) - sampling_info.update(cube_level_1, cube_level_1.is_sampled(f)) - sampling_info.update(cube_level_2, cube_level_2.is_sampled(f)) - - self.assertTrue(sampling_info.total_cubes == 3) - self.assertTrue(sampling_info.sampled_cubes == 2) - - self.assertTrue(sampling_info.total_rows == 250) - self.assertTrue(sampling_info.sampled_rows == 200) - - self.assertTrue(sampling_info.total_size == 11 / (1024 * 1024)) - self.assertTrue(sampling_info.sampled_size == 10 / (1024 * 1024)) + def test_link(self): + parent_cube = Cube(cube_id="", depth=0) + self.cube.link(parent_cube) + self.assertEqual(self.cube.parent, parent_cube) + self.assertIn(self.cube, parent_cube.children) diff --git a/utils/visualizer/tests/drawing_elements_test.py b/utils/visualizer/tests/drawing_elements_test.py deleted file mode 100644 index 8b4d6d5d4..000000000 --- a/utils/visualizer/tests/drawing_elements_test.py +++ /dev/null @@ -1,64 +0,0 @@ -import unittest -from qviz.drawing_elements import process_add_files, get_nodes_and_edges, populate_tree - - -class TestDrawingElementPreparation(unittest.TestCase): - def setUp(self) -> None: - self.add_files = [ - {"size": 1, "tags": {"state": "FLOODED", "cube": "", "elementCount": "100", "maxWeight": "-1717986918"}}, - {"size": 1, "tags": {"state": "FLOODED", "cube": "A", "elementCount": "50", "maxWeight": "2147483647"}}, - {"size": 1, "tags": {"state": "FLOODED", "cube": "g", "elementCount": "100", "maxWeight": "-1269647486"}}, - {"size": 1, "tags": {"state": "FLOODED", "cube": "g", "elementCount": "100", "maxWeight": "-1269647486"}} - ] - self.metadata = { - "desiredCubeSize": 10000, - "columnTransformers": [ - { - "className": "io.qbeast.core.transform.LinearTransformer", - "columnName": "user_id", - "dataType": "IntegerDataType" - }, - { - "className": "io.qbeast.core.transform.LinearTransformer", - "columnName": "price", - "dataType": "DoubleDataType" - } - ], - "transformations": [] - } - - def test_add_files_processing(self): - cubes = process_add_files(self.add_files, self.metadata) - - self.assertTrue(len(cubes) == 3) - for cube in cubes: - if cube.cube_string == "g": - self.assertTrue(cube.element_count == 200) - - def test_populate_tree(self): - cubes = process_add_files(self.add_files, self.metadata) - root = populate_tree(cubes) - - for c in root.children: - self.assertTrue(c.parent, root) - - def test_extract_nodes_and_edges(self): - cubes = process_add_files(self.add_files, self.metadata) - - elements = get_nodes_and_edges(cubes) - - nodes, edges = [], [] - for element in elements: - if 'source' in element['data']: - edges.append(element) - else: - nodes.append(element) - - node_names = sorted([node['data']['id'] for node in nodes]) - cube_strings = sorted([cube.cube_string or "root" for cube in cubes]) - for node_name, cube_name in zip(node_names, cube_strings): - self.assertTrue(node_name, cube_name) - - for edge in edges: - if edge['data']['source'] == 'root': - self.assertIn(edge['data']['target'], ["A", "g"]) diff --git a/utils/visualizer/tests/resources/table_with_checkpoint/_delta_log/00000000000000000010.checkpoint.parquet b/utils/visualizer/tests/resources/table_with_checkpoint/_delta_log/00000000000000000010.checkpoint.parquet deleted file mode 100644 index ee9e1071e0aa225faced10f79badd2447e9e8222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33435 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 {7^`W<~mqLPPXYYH@n>q^X-c_Bkouu0h!IKv2S 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