Compare commits
6 Commits
cdaaaad6af
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c4d2516ea5 | |||
| f19ccf349b | |||
| 0f8572b26e | |||
| 8b7610671c | |||
| 5f020258e8 | |||
| af38580a71 |
Generated
+304
-289
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 2.3.2 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
@@ -48,39 +48,39 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "26.3.1"
|
||||
version = "26.5.1"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2"},
|
||||
{file = "black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b"},
|
||||
{file = "black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac"},
|
||||
{file = "black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a"},
|
||||
{file = "black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a"},
|
||||
{file = "black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff"},
|
||||
{file = "black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c"},
|
||||
{file = "black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5"},
|
||||
{file = "black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e"},
|
||||
{file = "black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5"},
|
||||
{file = "black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1"},
|
||||
{file = "black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f"},
|
||||
{file = "black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7"},
|
||||
{file = "black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983"},
|
||||
{file = "black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb"},
|
||||
{file = "black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54"},
|
||||
{file = "black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f"},
|
||||
{file = "black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56"},
|
||||
{file = "black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839"},
|
||||
{file = "black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2"},
|
||||
{file = "black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78"},
|
||||
{file = "black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568"},
|
||||
{file = "black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f"},
|
||||
{file = "black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c"},
|
||||
{file = "black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1"},
|
||||
{file = "black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b"},
|
||||
{file = "black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07"},
|
||||
{file = "black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893"},
|
||||
{file = "black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90"},
|
||||
{file = "black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4"},
|
||||
{file = "black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef"},
|
||||
{file = "black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22"},
|
||||
{file = "black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c"},
|
||||
{file = "black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7"},
|
||||
{file = "black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59"},
|
||||
{file = "black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3"},
|
||||
{file = "black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe"},
|
||||
{file = "black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8"},
|
||||
{file = "black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217"},
|
||||
{file = "black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d"},
|
||||
{file = "black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264"},
|
||||
{file = "black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418"},
|
||||
{file = "black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3"},
|
||||
{file = "black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0"},
|
||||
{file = "black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294"},
|
||||
{file = "black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a"},
|
||||
{file = "black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52"},
|
||||
{file = "black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168"},
|
||||
{file = "black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3"},
|
||||
{file = "black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18"},
|
||||
{file = "black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50"},
|
||||
{file = "black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae"},
|
||||
{file = "black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2"},
|
||||
{file = "black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -151,14 +151,14 @@ redis = ["redis (>=2.10.5)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2026.4.22"
|
||||
version = "2026.5.20"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a"},
|
||||
{file = "certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580"},
|
||||
{file = "certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897"},
|
||||
{file = "certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -302,14 +302,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.3.3"
|
||||
version = "8.4.1"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main", "dev"]
|
||||
files = [
|
||||
{file = "click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613"},
|
||||
{file = "click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2"},
|
||||
{file = "click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2"},
|
||||
{file = "click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -537,72 +537,92 @@ sqlalchemy = ">=2.0.16"
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.5.0"
|
||||
version = "3.5.1"
|
||||
description = "Lightweight in-process concurrent programming"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""
|
||||
files = [
|
||||
{file = "greenlet-3.5.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:29ea813b2e1f45fa9649a17853b2b5465c4072fbcb072e5af6cd3a288216574a"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:804a70b328e706b785c6ef16187051c394a63dd1a906d89be24b6ad77759f13f"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:884f649de075b84739713d41dd4dfd41e2b910bfb769c4a3ea02ec1da52cd9bb"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4d0eadc7e4d9ffb2af4247b606cae307be8e448911e5a0d0b16d72fc3d224cfd"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b28037cb07768933c54d81bfe47a85f9f402f57d7d69743b991a713b63954eb"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:f8c30c2225f40dd76c50790f0eb3b5c7c18431efb299e2782083e1981feed243"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cda05425526240807408156b6960a17a79a0c760b813573b67027823be760977"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9c615f869163e14bb1ced20322d8038fb680b08236521ac3f30cd4c1288785a0"},
|
||||
{file = "greenlet-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:ba8f0bdc2fae6ce915dfd0c16d2d00bca7e4247c1eae4416e06430e522137858"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8f1cc966c126639cd152fdaa52624d2655f492faa79e013fea161de3e6dda082"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:362624e6a8e5bca3b8233e45eef33903a100e9539a2b995c364d595dbc4018b3"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5ecd83806b0f4c2f53b1018e0005cd82269ea01d42befc0368730028d850ed1c"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa94cb2288681e3a11645958f1871d48ee9211bd2f66628fdace505927d6e564"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ff251e9a0279522e62f6176412869395a64ddf2b5c5f782ff609a8216a4e662"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:64d6ac45f7271f48e45f67c95b54ef73534c52ec041fcda8edf520c6d811f4bc"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d874e79afd41a96e11ff4c5d0bc90a80973e476fda1c2c64985667397df432b"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0ed006e4b86c59de7467eb2601cd1b77b5a7d657d1ee55e30fe30d76451edba4"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:703cb211b820dbffbbc55a16bfc6e4583a6e6e990f33a119d2cc8b83211119c8"},
|
||||
{file = "greenlet-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:6c18dfb59c70f5a94acd271c72e90128c3c776e41e5f07767908c8c1b74ad339"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:db2910d3c809444e0a20147361f343fe2798e106af8d9d8506f5305302655a9f"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ec9ea74e7268ace7f9aab1b1a4e730193fc661b39a993cd91c606c32d4a3628"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54d243512da35485fc7a6bf3c178fdda6327a9d6506fcdd62b1abd1e41b2927b"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:41353ec2ecedf7aa8f682753a41919f8718031a6edac46b8d3dc7ed9e1ceb136"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d280a7f5c331622c69f97eb167f33577ff2d1df282c41cd15907fc0a3ca198c"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:58c1c374fe2b3d852f9b6b11a7dff4c85404e51b9a596fd9e89cf904eb09866d"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1eb67d5adefb5bd2e182d42678a328979a209e4e82eb93575708185d31d1f588"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2628d6c86f6cb0cb45e0c3c54058bbec559f57eaae699447748cb3928150577e"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4d9f0624c775f2dfc56ba54d515a8c771044346852a918b405914f6b19d7fd8"},
|
||||
{file = "greenlet-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:83ed9f27f1680b50e89f40f6df348a290ea234b249a4003d366663a12eab94f2"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:5a5ed18de6a0f6cc7087f1563f6bd93fc7df1c19165ca01e9bde5a5dc281d106"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a717fbc46d8a354fa675f7c1e813485b6ba3885f9bef0cd56e5ba27d758ff5b"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ddc090c5c1792b10246a78e8c2163ebbe04cf877f9d785c230a7b27b39ad038e"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4964101b8585c144cbda5532b1aa644255126c08a265dae90c16e7a0e63aaa9d"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2094acd54b272cb6eae8c03dd87b3fa1820a4cef18d6889c378d503500a1dc13"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:7022615368890680e67b9965d33f5773aade330d5343bbe25560135aaa849eae"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5e05ba267789ea87b5a155cf0e810b1ab88bf18e9e8740813945ceb8ee4350ba"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0ecec963079cd58cbd14723582384f11f166fd58883c15dcbfb342e0bc9b5846"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:728d9667d8f2f586644b748dbd9bb67e50d6a9381767d1357714ea6825bb3bf5"},
|
||||
{file = "greenlet-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:47422135b1d308c14b2c6e758beedb1acd33bb91679f5670edf77bf46244722b"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2"},
|
||||
{file = "greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86"},
|
||||
{file = "greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:7eacb17a9d41538a2bc4912eba5ef13823c83cb69e4d141d0813debe7163187f"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e5cc9606aa5f4e0bde0d3bd502b44f743864c3ffa5cfa1011b1e30f5aa02366f"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c3d35f87c7253b715d13d679e0783d845910144f282cb939fe1ba4ac8616269c"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:00929c98ec525fd9bf075875d8c5f6a983a90906cdf78a66e6de2d8e466c2a19"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:540dae7b956209af4d70a3be35927b4055f617763771e5e84a5255bea934d2f5"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-manylinux_2_39_riscv64.whl", hash = "sha256:001775efe7b8e758861294c7a27c28af87f3f3f1c20468a2bc618c45b346c061"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ed8cdb691169715a9a492844a83246f090182247d1a5031dc78a403f68ba1e97"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d59e840387076a51016777a9328b3f2c427c6f9208a6e958bad251be50a648d"},
|
||||
{file = "greenlet-3.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:b9152fca4a6466e114aaec745ae61cba739903a109754a9d4e1262f01e9259b1"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:73f78f9b9f0a5c06e5c946ba1e8e36f5114923b6be109ee618c54f079c3ea14f"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0cbed8bb44e23c5b199f888f4e4ce096b45ad9f25ff74a7ad0213875e936bb2"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a203a8bd0acb0701653d3bbb26e404854a68674139ed5cbb778830f42b09bb33"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ebeb75c81211f5c702576cf81f315e77e23cfdb2c7c6fcb9dd143e6de35c360"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a271fcd66c74615cda6a964fda3f304267a12e50a084472218a39bb0376f563"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:017a544f0385d441e88714160d089d6900ef46c9eff9d99b6715a5ef2d127747"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ded7b068c7c31c1a8657d4fd42d886b3e051ae29f88b80c5ff9d502257b0f071"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0932b81d72f552ded9d810d00021b64d89f2195a91ce115b893f943b7a4ab3c"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:88e300d136eac057b2397aa1cfd7328b4c87c7eb66a09c7bc6a1292234db474e"},
|
||||
{file = "greenlet-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:cc6ab7e555c8a112ad3a76e368e86e12a2754bcae1652a5602e133ec7b635523"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:fa4f98af3a528f0c3fd592a26df7f376f93329c8f4d987f6bb979057af8bf5e2"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffea73584b216150eab159b6d12348fb253e68757974de1e2c40d8a318ac89ed"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1072b4f9edcc1e192d9283a66a3e68d6b84c561de33a83d7858beb9ba1effe10"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89101bfd5011e069be974903cb3a4e4523845e4ece2d62dcd8d358933c0ef249"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:add5217d68b31130f0beca584d7fef4878327d2e31642b66618a14eef312b63b"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:e6cd99ea59dd5d89f0c956606571d79bfe6f68c9eb7f4a4083a41a7f1587edee"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5ea42a752d47a145eae922b605cd1634665ac3d5ec1e72402d5048e8d60d207"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5551170cf4f5ff5623e9af81323751979fee2c731e2287b61f73cd27257b823"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c8bb982ad117d29478ef8f5533e97df21f1e2befd17a299257b0c96d1371c0b"},
|
||||
{file = "greenlet-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:80eb4b04dadc4e67df3fae179a32c4706a3f495bc7f22fc8a81115d5f5512188"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9"},
|
||||
{file = "greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78"},
|
||||
{file = "greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e"},
|
||||
{file = "greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a"},
|
||||
{file = "greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -635,14 +655,14 @@ tornado = ["tornado (>=6.5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.15"
|
||||
version = "3.16"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8"},
|
||||
{file = "idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc"},
|
||||
{file = "idna-3.16-py3-none-any.whl", hash = "sha256:cc246e3a3f89580c3a951b5ad298ca4638078b2cdd4f115654332b5c26daded5"},
|
||||
{file = "idna-3.16.tar.gz", hash = "sha256:d7a6da03db833450fca25d2358ac9ff06cd624577a4aea3a596d5c0f77b8e03d"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -704,7 +724,7 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
attrs = ">=22.2.0"
|
||||
jsonschema-specifications = ">=2023.3.6"
|
||||
jsonschema-specifications = ">=2023.03.6"
|
||||
referencing = ">=0.28.4"
|
||||
rpds-py = ">=0.25.0"
|
||||
|
||||
@@ -1150,143 +1170,143 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.4.5"
|
||||
version = "2.4.6"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.11"
|
||||
groups = ["pi"]
|
||||
files = [
|
||||
{file = "numpy-2.4.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3176dc8ff71dbb593606f91a69ad0c3cd3303c7eb546af477370ab9edf760288"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1811150e5148f5a01a7cc282cb2f489b4a3050a773e173adb480e507bad3a3d7"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0d63a780070871210853ba01e90b88f9b85cf2abf63a7f143d5127189265ddf6"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:0c6919cefafb3b76cd46a89dbb203bf1dd95529d2a6d09fef2d325d95d6a79d8"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d51efede1e58e8b11877536a5518f60e318d8ff69b89ad7b38ee5e431b24d772"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07ce7e74da92d7c71b5df157b9758bcdd53d7fea10602154de3afd2b3ddc34dd"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d7828234a13185effb34979e146f9921f2a65dfbbe215e6dbb57d6478fc8e059"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f96083adc3dfc1bbf778f2c79654d88115fa07074c97cb724fe9508f12d91c55"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-win32.whl", hash = "sha256:4ed78c904a638b6e5d7cd4db90c06fca5fc6ec2f28d258305368f454a50e79cf"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:079b0fad6f2899b23c5da89792b5409d2d83fc83e8bd5c2299cc9c397a264864"},
|
||||
{file = "numpy-2.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:d6c78e260b53affe9b395a9d54fc61f101f9521c4d9452c7e9e3718b19e2215b"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:654fb8674b61b1c4bd568f944d13a908566fdcb0d797303521d4149d16da05ef"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4cd9f6fa7ce10dc4627f2bb81dd9075dab67e94632e04c2b638e12575ddaa862"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:4f5bc96d35d94e4ceab8b38a92241b4611e95dc44e63b9f1fa2a331858ee3507"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4bb33e900ee81730ad77a258965134aa8ceac805124f7e5229347beda4b8d0aa"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32f8f852273ef32b291201ac2a2c97629c4a1ee8632bb670e3443eaa09fc2e72"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685681e956fc8dcb75adc6ff26694e1dfd738b24bd8d4696c51ca0110157f912"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f64dd84b277a737eb59513f6b9bb6195bf41ab11941ef15b2562dbab43fa8ef"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b42d9496f79e3a728192f05a42d86e36163217b7cdecb3813d0028a0aa6b72d7"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-win32.whl", hash = "sha256:86d980970f5110595ca14855768073b08585fc1acc36895de303e039e7dee4a5"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:3333dba6a4e611d666f69e177ba8fe4140366ff681a5feb2374d3fd4fff3acb6"},
|
||||
{file = "numpy-2.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:4593d197270b894efeb538dcbe227e4bcf1c77f88c4c6bf933ead812cfaa4453"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1ef248460b645c102026b82337cc4e88231909c66dd77b59ec6d6cac7e44f277"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4603622bdcdbf8dccb1d9d5b21d16a7aa4e473ae6c8e14048d846fd4ca2907a0"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6c18d49c67689c562854b53fdc433b93e47c12952aa6fa6d59f185e1a5992419"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b1c663ddc641f4192e90511bec61a09bc231e3bbdb996cdc6edbcaa0e528d685"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93793222b524f692f12b2f8752ce8b1d9d9125b2bfd5dbf0fb69c92c5e1ce86c"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1616bde34b2bcba2fa9bde06217ce00da4f3d1bdfb264d54525a99e8fe170d83"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:09d7d97da1c2c62f4818b3e150a57572ff8dcf1cf5ac501aac832ffd4ebd9566"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d68d0b355ab2e39fe0de59001d7151dfdbbb880ef67baeed806661e03df5097"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-win32.whl", hash = "sha256:fe28b64777ddfa0eca9b5f51474034ebe3dcb8324f48f27b28f479085673ae33"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:fb4a6c9c537d6ccec9cc4aeae4261bd3cc79b070c67ddc0646f5b1c07fddde42"},
|
||||
{file = "numpy-2.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:6d7df2da2e7ea0624a43aa368104b3a3ce14aae98ad4bb2c9a93fecef76f1c97"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:2a235607a18df941760a695927051af4b1cd5d3ee85840d0e2af816785771feb"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:58dcf64969d870f36bc7fbd557d2617e997db7dc06261b6e3327148ea460d0a4"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:235f54b0156274d8fa3155db3ed6d2f401c7e8f3367c90db0a12f02a58fde6ed"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3b5bb65437a3555c648e706475db01c645559ca80dc8b03e4f202ea757e0d6"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7f09a7e5f017d7098c66522097c96257411c9620c0926212200d66bc8cee3976"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:993a88d8fdd8554466a8765cd8bacd97ba56b70ca6b0a04bcdca77f5afed4222"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:84f58bed609b5669f5ad3d597901a4f1f86ee5b3c3708aaa55f05b4fe6e0f656"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-win32.whl", hash = "sha256:7200c58f3f933ca61e66346667dcc8510bb111995e9ce15398a731e6a4afa4bb"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c26c71080d35db5002102f5d9ff614d45de02aa1f7802943e691e063e5ee93bc"},
|
||||
{file = "numpy-2.4.5-cp313-cp313t-win_arm64.whl", hash = "sha256:2caa576d1707b275cba1aeb60a5c50daa6fa2a3f28ecb08123bc05fd439005db"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:889ca2c072315de638a5194a772aa1fa2df92bdd6175f6a222d4784040424b61"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:89e89304fb1f8c3f0ecfa4a7d48f311dd79771336a940e920159d643d1307e77"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:144fcc5a3a17679b2b82543b4a2d8dd29937230a7af13232b5f753872feb6361"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:398bb16772b265b9fa5c07b07072646ea97137c10ffb62a9a087b277fc825c29"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb352e7b8876da1249e72254736d6c58c505fa4e58a3d7e30efca241ca9ca9ce"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7341b08ff8124d7353939778e2707b8732d03c78c1c30e0815aba2dacbe1245a"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:deb01226f012539f3945261ffe1c10aec081a0fa0a5c925419933c70f3ae2d23"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d888bdf7335f76878c3c7b264ac1ff089863e211ec81249f9fb5795c2183dc25"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-win32.whl", hash = "sha256:15f90d1256e9b2320aff24fde44815b787ab6d7c49a1a11bfd8138b321c5f080"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4bd2cd4ef9c0afa87de73723c0a33c0edff62143e1432917458e26d3d195d87f"},
|
||||
{file = "numpy-2.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:db304568c650e9d7039744d3575d0d287754debb2057d7c7b8cdfdc2c487a957"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6de2883e0d2c63eae1bab1a84b390dca74aabb3d20ea1f5d58f360853c83abf3"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:06760fe73ae5005008748d182de612c733542af3cde063d532cd2127561b27be"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:4b51a01745cb04cc19278482207444b4d30728ce91c28d27a3bfae5fc6ff24c7"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a05636d7937d0936f271e5ba957fa8d746b5be3c2025caa1a2508f4fe521d40"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b86f56048ed09c3bbe48962a7dff077c2fd3274f8cf981800f3b38eac49cc3"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:130d58151c4db23e9fa860b84784e219a3aa3e030acc88a493ea37006c4dfd4c"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d475afc8cbe935ff5944f753d863bba774d7f4e1feaaa4102901e3e053ca5963"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-win32.whl", hash = "sha256:27f4a6dc26353a860b348961b9aa9e009835688b435cfa105e873b8dc2c726f5"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-win_amd64.whl", hash = "sha256:76ac6e90f5e226011c88f9b7040a4bcae612518bc7e9adc127e697a13b28ad1a"},
|
||||
{file = "numpy-2.4.5-cp314-cp314t-win_arm64.whl", hash = "sha256:7c392e2c1bf596701d3c6832be7567eab5d5b0a13865036c33365ee097d37f8b"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6bf0bfc1c2e1db972e30b6cd3d4861f477f3af908b27799b239dc3cbe3eb4b95"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:73d664413fb97229149c4711ef56531a6fe8c15c1c2626b0bbe497b84c287e70"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:b35bee5ef99e8d227a07829bee2e864fcb65f7c157646fcd8ec8b4b45dd8b88f"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:02981d0fc9f9ce147643d552966d47f329a02f7ecb3b113e84207242f20dfa83"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e63caf31a1df06338ae63d999f7a33a675ced62eea9c9b02db4b1c1f45cff38"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d8fc52b85a7b45e474be53eddf08e006d22e381a4e41bcde8e4aa08da0e7d198"},
|
||||
{file = "numpy-2.4.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:40c71d50a4da1a7c317af419461052d3911a5770bfc5fd55baf52cc45e7a2c20"},
|
||||
{file = "numpy-2.4.5.tar.gz", hash = "sha256:ca670567a5683b7c1670ec03e0ddd5862e10934e92a70751d68d7b7b74ca7f9f"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147"},
|
||||
{file = "numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751"},
|
||||
{file = "numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359"},
|
||||
{file = "numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75"},
|
||||
{file = "numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261"},
|
||||
{file = "numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627"},
|
||||
{file = "numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02"},
|
||||
{file = "numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73"},
|
||||
{file = "numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openexr"
|
||||
version = "3.4.11"
|
||||
version = "3.4.12"
|
||||
description = "Python bindings for the OpenEXR image file format"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["pi"]
|
||||
files = [
|
||||
{file = "openexr-3.4.11-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:677a6988dc2dca7756b660f9c9dfbfa4497552f9b90285df0f9cd5b780bda901"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:e241db758acc7db68e07034d274545b1a05f5130b6d4702eda68a0b4f7e0452f"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7945e9e954405574a40e6e3930c35295287c849c7a78679b39614d4d138383b3"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f183257d1c7f80525195226e1627b7132e8ed7628edd0d11c81fec130a7f06be"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1502e90b2e2cc4134846e208a9a3a9b3cfca886c681a759d68b0c2c0fbfcb526"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b3effb270004b8af068426f223edb2009909a51d2970da3b0576a2db09339377"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e5341501f889e8b0d768c677645eedf949847ae82dac027b414157a866812b6"},
|
||||
{file = "openexr-3.4.11-cp310-cp310-win_amd64.whl", hash = "sha256:47ef1dc8e75ba33af6cb4b2880e006ec6e27911d0f9c4a477c4501e7f4994c61"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:502f8d2e32928a475316730569fa9fcfd22d4258d3f40afe253db4fcd258f02e"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:e64b2ad85f14897580020eb2f5519e7b1ba96892c468934e65edb3bebc66cb68"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c0566a70859a6f237b25034b83b832a6c67a63ea2224094f54b208d1462ca749"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bc04f405f428b511fda8403ae22443ae1f6bcca98256cff49b5bca817e8c89dd"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cffbb450c121cdf026e9405c590e9b70d5ca1b519da51cd955f1c7541dc5ac2f"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe8493a34d08b9169445aef13dcfeded0285a038b8c3f125bec9fe606f1b3b25"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5dd631d7df0838073a65f2e52a6333e448cc60dadcca57df33186cc6140080df"},
|
||||
{file = "openexr-3.4.11-cp311-cp311-win_amd64.whl", hash = "sha256:6c5a791e9835e3bc1c58e054ea5402d4fda371a1c1aacd75f9580e52d0f39455"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:c11187b2b7818abcd4caae98939a192409de7032900305540f124ab02a122f2d"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:d0faef97e93a539a7de59b247c22b19037f3adbf17e61790f61cfd1ca631e68a"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9a16d54d1fb65cefd73189c7d7f2437385563a297129fefcae3601b31704b8ac"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a5baebe08bcf294feb50ceae07456302e3d87886b27cafcabe2e81dce3dc1214"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6b5f90d8ca262119e1ca626ee9ccc92bd1859cff38c19e3ba14c0353ec1352f9"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c90d565b37c53e9b644ef454a02642cea6e3b3f22de7f638f4346cefd782b9ca"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:26a9fe29d65aa7e7086e9d89f3c416cc66c0fb2b211b3b98d1df95b25c276b5a"},
|
||||
{file = "openexr-3.4.11-cp312-cp312-win_amd64.whl", hash = "sha256:65a3a5d456ce33f79fdaba5c9b5e28837164b6e672f3866bbd8c940c69abc1fb"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:fb1ef1a5ba9293efbfc5ceda38ed45847b484bfbf21391d5401f61a613e55b16"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:911d73a84178ade1b7757c262da62a8a45fa241502a83f3af6c84c89c518c4f7"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ea1e90291a48515f3079789be13b23a0976ba3b8522dd71a812f419d19d4246a"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c28ccb6c69374c7834600bbb43546949f2e670268eca400703535d89e0da3e6"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:41deb09f525fb3ad7cb9f114953f7343cf28ae65ac3b7fdd9a3f3429c590ad19"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:168a213f9c5ecda7e9fe44f4f05efb5516a19151ac55274e3ceda930535c3ca7"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a40912694f33e60a4991f4d26a046350eede26197b216ed33983a400326195c"},
|
||||
{file = "openexr-3.4.11-cp313-cp313-win_amd64.whl", hash = "sha256:758844e0f77eab48d3e1accb912ec5cb9c726af6fb0b8812c141e1eedcf91be0"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:8c0c53a901eff9bbcd97a9a2b20ea882b23f17dd0579a8d93490c5b13bc48536"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3ed4a9951f6e77bf7d28f3ec1b7de1e60ae7607af7a2201d0d4d97bb4a3cbc32"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fc41450bce5a25030564d865f73815de4db3ee25e64414b728b6ce6c15bd963a"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c0d8495cba2e3cac262d8e5a3f478fdf98cb79b3a294c48709e7734fe7110b60"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d7cf1fd0ccef5baf25a07fda0a340e0536bb87f584172125343da2678a4252da"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8abd9216e65b59629f946a89e97cb3eadbf31c11b306bc32438c7abce2aa6438"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:65d50df2bb85fd51c9dae8aac6e1c4bbef1c5d0a28934996186a24327ad0e349"},
|
||||
{file = "openexr-3.4.11-cp38-cp38-win_amd64.whl", hash = "sha256:7e57e3f2e9a573f23f3a2d9036dc58bb8eef27c04bbe4af1585a3c462700af5b"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:cb6a7f7eb30b511d0135d2f03c233a78248680b11e63a11c42432d2615c577f0"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:7717c323030ebcf7976c50889cc83fe44b6e00b1d7c53e68a4e859ce426494d1"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be6fa46e0b93a2d419e01c0862a73f3c4051a660381200a13566fa37fd098d50"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:757461bd5fede940c08d8c44b54529e573f87e521923bc860fc5bcbac8365d08"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5e9a181377af663161323bdf4907e50bc4dcf918dc793989da9c3d7ccc39ca1a"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:682f515547588aa9834a77f9beebb617378f55013f47f0dd3388cda4028ab57e"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3a35e8c545309d7d7b527b14a1c37561f2c6aab33b26f4402b5c29f7f82b04c3"},
|
||||
{file = "openexr-3.4.11-cp39-cp39-win_amd64.whl", hash = "sha256:4dd1e3821848d0a5d0a5f6cb72f38b50a1ecb2c85c5ebc62d62c0a1df99ad4f2"},
|
||||
{file = "openexr-3.4.11.tar.gz", hash = "sha256:c32233f4ffea51ca3fe05a3f0193b24bf21d7cab04a46c292badc7c9dfa60851"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:85108cc4285996449ac43c952264b318c7062c18ad22da6125a1d77978057448"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:68bc8c0e2ef6a451b6321b6005d4698fa4260f762495dd404b3691dbd01b394c"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:06c1efcc9d1c6af354bc3c7c14114096814e8074830d78f64b7ec44033374e9b"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d0a9158bff791442a710b5742e1cef431f68fadcdc6fc37dde9db24175b3cfa2"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8a1760bfb3ca2f71a9c99f4cf13392457c54d60d61eb7b3b64a7b876be2df7d6"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3c082f5c16eadd2b9bbdc3bb812f257a33ef14f2752bbaec2eaeeac34887080e"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:52eaf6805a092d90a49a89b909cc758c6da3bda888eee46bb8ce0c7c2444873c"},
|
||||
{file = "openexr-3.4.12-cp310-cp310-win_amd64.whl", hash = "sha256:6ce8e8b430576ba3d274beef50753c5a3ebcd646ea3db2b855ed4a0a2a3bcf11"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:eb0aa7036a5c1b022073ad1955780337a381f4fd3d7fc85b31263090240e7102"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:5c3c689c7973cc55621cf835314b7437517fef54f20046b8a594adce1b14e1e4"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:983ab3a11ca74bae35d298c09cfa979f16646cd950fc4defa5620cf6ef9c58c9"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2417aad023c4946d0c29bbb98fe605504b83842081bdfe0a7f1fb190202190a1"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4e7b52c1a999ec7c1a32ad01197ee4a41c0b81924fdad105b684ff06bfef4e67"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0c312a67896a874a07c87f7098da139d47406e5ceca6915282eec9e252ce3449"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b736108b27aa3acd6f52d523d596627bc1e7366053d292442769d4ee4251ed2"},
|
||||
{file = "openexr-3.4.12-cp311-cp311-win_amd64.whl", hash = "sha256:ed544f1eced73568d15a00140b94d502e4e5b7f7f2ec142269aeb45338596f09"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:efab1439a2d160837b68aa26053d6145e85cc326f086f5f62ecd05946cf769a5"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:f306aca0d2dd5fa157d2cb0f5e1fdd8ee9e126fc85bd31a34ff39533d1943b05"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b975e643a790e0e4f9f3de3dd9210a51c1c65b555ed4f7653f059fc32675fa0a"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80f75a668b2fe66d961a234157245d98f4172837a339d0ce10d97663738a5d00"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:222aca5d3526910b6a75b522d9dce5946d64159aa016472353b912c33cb1cd59"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3a01ecb034caddde76191944b7965b48179d635ac4cc3bdbf33034b45153a4b4"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa3f711c2f8683de7c8150fe6038b5bd4729430bc308826e406b64b718fc86f3"},
|
||||
{file = "openexr-3.4.12-cp312-cp312-win_amd64.whl", hash = "sha256:8a2eaed1d869fd6c2cecacb4807e8482a5ee30ac39a6d33373ab4718c5e171b3"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:3031ed47d3a579d3fa6998d0a6dad22012ce9fd6a33485fa5339ca4a079d0665"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:059f666c43a5d255abc0f9d6f87f2c5ca6ba462fc0442316a672e0ecbcf6a4a9"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:553f4267221490e846b7f63edcca807fac1757cd242a49063f048fd0e757029c"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4dc03aa88a57d47c49780bee40c4f0febabf8ed150e2bd60e55f3ee6bea62493"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8301110402427ea48073ab11556873c0ba3dc8d1d6fdc79fd77a7738a50eeb11"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b8be14c4794f4ad19112fbbed7767c5bf6216435f27cb42a499cdab0f3d26562"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8feae6511257a2a33aa067c247a06b82e31bbbadfafe82a14ab5a7288a93b16b"},
|
||||
{file = "openexr-3.4.12-cp313-cp313-win_amd64.whl", hash = "sha256:19f6ac86915cd93ee42b615702477435d9fbbdd9da217c3f70d8a04586240759"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:5a7c007fdb33d4c1aa2fa02e709df23635a82d656cde2d78574659b6a47d0a8c"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3db04ec227efbda710c38b60f53a8a06897ba2f24664911f54fd79367ee1a540"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dcf1eb54fb91887664ac03fee187ccbe542a95ac29ddba4c141d84496b891af8"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b6f6284d7c41611a92845a9fb47c930a80c4f726cef198aa6b3e501ac47ef3d4"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6f988539c6704acd9a4167afe10bda77c3c6d87067f159e592ffda2d934a0264"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:3a4c9cf3280107b0a264f55c19e3577aa8ae33dcc3c34d0741a0f74bd6fd4ff4"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:af9971ee6a28f6f4ad39c8fa349dd36bf8d2d8aa9678b040b6e42fe34fcd175f"},
|
||||
{file = "openexr-3.4.12-cp38-cp38-win_amd64.whl", hash = "sha256:399e2fb0747f52045cdb1bb5572ea337e2c059830cc3fe6487009df5cbb29d99"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:41b431606600cce78c18d9874dcf2c1806835e49dfec959d0f7674fa786ee5a2"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:098bc33f47f2ccf54f462789cfebf2954b2a9c0544f1821276a0c1c7ade7b9f8"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:106f8f36207543dcc9bb60e77e072b8fb3a5eb5a7dc82028dba2f9f90fdd50a5"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4e4c6c39d693a7bd30bb37b7408aa720ceebf7352ee0b91be9f79e3664ad97c1"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:97170ba4d09bf5b2718f13bc362372006d26b547c5090ad6507715f2ef43ef4d"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:71dc88981fa83778c6a51e7fe4580da2cd2320bd85153f2c761577db215f3494"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7a8e2c2b43f1b1c730e72e66674b7d645b64808e18bf6daf50fcbb7b4491b8fc"},
|
||||
{file = "openexr-3.4.12-cp39-cp39-win_amd64.whl", hash = "sha256:d9767fd77a3401da3224999b0952053b9784416d0d91f223ea0c3dc8e441c664"},
|
||||
{file = "openexr-3.4.12.tar.gz", hash = "sha256:877da800b30146e5e29851da2a80147883244966f5b2e932e04f1f1a06ff4fc7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2057,75 +2077,70 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy"
|
||||
version = "2.0.49"
|
||||
version = "2.0.50"
|
||||
description = "Database Abstraction Library"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:42e8804962f9e6f4be2cbaedc0c3718f08f60a16910fa3d86da5a1e3b1bfe60f"},
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc992c6ed024c8c3c592c5fc9846a03dd68a425674900c70122c77ea16c5fb0b"},
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eb188b84269f357669b62cb576b5b918de10fb7c728a005fa0ebb0b758adce1"},
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:62557958002b69699bdb7f5137c6714ca1133f045f97b3903964f47db97ea339"},
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da9b91bca419dc9b9267ffadde24eae9b1a6bffcd09d0a207e5e3af99a03ce0d"},
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-win32.whl", hash = "sha256:5e61abbec255be7b122aa461021daa7c3f310f3e743411a67079f9b3cc91ece3"},
|
||||
{file = "sqlalchemy-2.0.49-cp310-cp310-win_amd64.whl", hash = "sha256:0c98c59075b890df8abfcc6ad632879540f5791c68baebacb4f833713b510e75"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5070135e1b7409c4161133aa525419b0062088ed77c92b1da95366ec5cbebbe"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ac7a3e245fd0310fd31495eb61af772e637bdf7d88ee81e7f10a3f271bff014"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d4e5a0ceba319942fa6b585cf82539288a61e314ef006c1209f734551ab9536"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ddcb27fb39171de36e207600116ac9dfd4ae46f86c82a9bf3934043e80ebb88"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:32fe6a41ad97302db2931f05bb91abbcc65b5ce4c675cd44b972428dd2947700"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-win32.whl", hash = "sha256:46d51518d53edfbe0563662c96954dc8fcace9832332b914375f45a99b77cc9a"},
|
||||
{file = "sqlalchemy-2.0.49-cp311-cp311-win_amd64.whl", hash = "sha256:951d4a210744813be63019f3df343bf233b7432aadf0db54c75802247330d3af"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0"},
|
||||
{file = "sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d"},
|
||||
{file = "sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158"},
|
||||
{file = "sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8a97ac839c2c6672c4865e48f3cbad7152cee85f4233fb4ca6291d775b9b954a"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c338ec6ec01c0bc8e735c58b9f5d51e75bacb6ff23296658826d7cfdfdb8678a"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:566df36fd0e901625523a5a1835032f1ebdd7f7886c54584143fa6c668b4df3b"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d99945830a6f3e9638d89a28ed130b1eb24c91255e4f24366fbe699b983f29e4"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:01146546d84185f12721a1d2ce0c6673451a7894d1460b592d378ca4871a0c72"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-win32.whl", hash = "sha256:69469ce8ce7a8df4d37620e3163b71238719e1e2e5048d114a1b6ce0fbf8c662"},
|
||||
{file = "sqlalchemy-2.0.49-cp38-cp38-win_amd64.whl", hash = "sha256:b95b2f470c1b2683febd2e7eab1d3f0e078c91dbdd0b00e9c645d07a413bb99f"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43d044780732d9e0381ac8d5316f95d7f02ef04d6e4ef6dc82379f09795d993f"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d6be30b2a75362325176c036d7fb8d19e8846c77e87683ffaa8177b35135613"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d898cc2c76c135ef65517f4ddd7a3512fb41f23087b0650efb3418b8389a3cd1"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:059d7151fff513c53a4638da8778be7fce81a0c4854c7348ebd0c4078ddf28fe"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:334edbcff10514ad1d66e3a70b339c0a29886394892490119dbb669627b17717"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-win32.whl", hash = "sha256:74ab4ee7794d7ed1b0c37e7333640e0f0a626fc7b398c07a7aef52f484fddde3"},
|
||||
{file = "sqlalchemy-2.0.49-cp39-cp39-win_amd64.whl", hash = "sha256:88690f4e1f0fbf5339bedbb127e240fec1fd3070e9934c0b7bef83432f779d2f"},
|
||||
{file = "sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0"},
|
||||
{file = "sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7af6eeb84985bf840ba779018ff9424d61ff69b52e66b8789d3c8da7bf5341b2"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fe7822866f3a9fc5f3db21a290ce8961a53050115f05edf9402b6a5feb92a9f"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e1b0f6a4dcd9b4839e2320afb5df37a6981cbc20ff9c423ae11c5537bdbd21"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e195687f1af431c9515416288373b323b6eb599f774409814e89e9d603a56e39"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ea1a8a2db4b2217d456c8d7a873bfc605f06fe3584d315264ea18c2a17585d0b"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-win32.whl", hash = "sha256:68b154b08088b4ec32bb4d2958bfbb50e57549f91a4cd3e7f928e3553ed69031"},
|
||||
{file = "sqlalchemy-2.0.50-cp310-cp310-win_amd64.whl", hash = "sha256:66e374271ecb7101273f57af1a62446a953d327eec4f8089147de57c591bbacc"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1aa6e403663a9c43c8fef7ce4bdb4cf48bcd8d352e91deda2a99f963270bd508"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51b637a84f9fa35ae1f9017e786cb142974a25305085e1b378b3647a67f65ad3"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2dab927761d9108550f0cf8e66ff21af56f907a0ce0a689793db615e2b55f62c"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:545eae198d37bcf837a10ede3684e2af32458d6f35c597c35c2de7502dc38fc4"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fec460e18cdbb4c7773531122ce9a27e96c6ca17af3933941d94da475ad2c86"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-win32.whl", hash = "sha256:e6e814658818fd165e749e3d8490ef16cc7f379a118c37ada8b0589ffbaaac22"},
|
||||
{file = "sqlalchemy-2.0.50-cp311-cp311-win_amd64.whl", hash = "sha256:1c5f858fe79c9f5d8fda065c06186356acb7f8df3cd52dbd5ee3f200e4b144f5"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae23d8b9d344d30d0a92f06d45825024a5790f1c1dd4cf452636a50d3e58cb"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47b71b933e7b4ebad407c8fdfd70d2c4f08b78b3238bb30eebdd6eb32ca51b89"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:110fdac56ace278949f00de805edacbd6141e382d992f9ba28238b3a0827a600"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0f5e4ac70e9e757f6b3e87c0491ff034442ecd8dfd36d041a50564c322dafc0e"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:724f3dcbe53dd0151e3cb5e7ec4ba4c620bede579caacd16275dc35ce06e8615"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-win32.whl", hash = "sha256:1208050441471d003b7c8cb4054fb084f185cf35ac3f0ea270803865bca9939a"},
|
||||
{file = "sqlalchemy-2.0.50-cp312-cp312-win_amd64.whl", hash = "sha256:9d1af51558029a156a70986b7df88f042b3d158d7c8d8fb5072912d4b32d89c7"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5"},
|
||||
{file = "sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e"},
|
||||
{file = "sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8e8af330cbb3a1931d3d6c91b239fc2ef135f7dd471dfa34c575028e0b1fa8"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eefd9a03cc0047b14153872d228499d048bd7deaf926109c9ec25b15157b8e23"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b85b20f9ab714a666df9d8e72e253ec33c16c7e1e375c877e5bf6367a3e917"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:27b7062af702c61994e8806ad87e42d0a2c879e0a8e5c61c7f69d81dabe24fdf"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2c1920cde9d741ba3dda9b1aa5acd8c23ea17780ccfb2252d01878d5d0d628d3"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-win32.whl", hash = "sha256:7b1ddb7b5fc60dfa9df6a487f06a143c77def47c0351849da2bcea59b244a56c"},
|
||||
{file = "sqlalchemy-2.0.50-cp38-cp38-win_amd64.whl", hash = "sha256:0e104e196f457ec608eb8af736c5eb4c6bc58f481b546f485a7f9c628ee532be"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:409a8121b917116b035bedc5e532ad470c74a2d279f6c302100985b6304e9f9e"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9602c07b03e1449747ecb69f9998a7194a589124475788b370adce57c9e9a56e"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d10700bd519573f6ce5badbabbfe7f5baea84cdf370f2cbbfb4be28dfddbf1d"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7e36efdcc5493f8024ec873a4ee3855bfd2de0c5b19eba16f920e9d2a0d28622"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:adc0fe7d38d8c8058f7421c25508fcbc74df38233a42aa8324409844122dce8f"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-win32.whl", hash = "sha256:0a31c5963d58d3e3d11c5b97709e248305705de1fdf51ec3bf396674c5898b7e"},
|
||||
{file = "sqlalchemy-2.0.50-cp39-cp39-win_amd64.whl", hash = "sha256:83a9fce296b7e052316d8c6943237b31b9c00f58ca9c253f2d165df52637a293"},
|
||||
{file = "sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9"},
|
||||
{file = "sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2259,7 +2274,7 @@ files = [
|
||||
{file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
|
||||
{file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
|
||||
]
|
||||
markers = {pi = "python_version == \"3.12\""}
|
||||
markers = {pi = "python_version < \"3.13\""}
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
@@ -2315,4 +2330,4 @@ watchdog = ["watchdog (>=2.3)"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = "^3.12"
|
||||
content-hash = "a740db635fb8417cf7ef30241e8a997dc331f5e692c90cc286a614be30166dce"
|
||||
content-hash = "f1e7faffc35767e0d68491449140c11c7787b5b65073b9d49865daf22a4d59db"
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ picamera2 = "^0.3"
|
||||
pytest = "^9.0.3"
|
||||
pytest-cov = "^7.1.0"
|
||||
pytest-flask = "^1.3"
|
||||
black = "^26.0"
|
||||
black = "^26.5.0"
|
||||
ruff = "^0.4"
|
||||
mypy = "^1.10"
|
||||
pip-audit = "^2.10.0"
|
||||
|
||||
+65
-4
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
from flask import (
|
||||
Flask,
|
||||
@@ -12,8 +15,8 @@ from flask import (
|
||||
)
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from src.camera import camera
|
||||
from src.database import CameraEvent, CameraStatus, db
|
||||
from src.camera import RECORDINGS_DIR, camera
|
||||
from src.database import CameraEvent, CameraRecordingEvent, CameraStatus, db
|
||||
|
||||
logging.basicConfig(level=logging.WARNING)
|
||||
|
||||
@@ -33,7 +36,6 @@ def create_app() -> Flask:
|
||||
# sync camera state with db on startup
|
||||
status = CameraStatus.get()
|
||||
if status.running and not camera.running:
|
||||
# was running before restart — mark as stopped
|
||||
CameraStatus.set_running(False)
|
||||
|
||||
return flask_app
|
||||
@@ -50,6 +52,9 @@ def get_client_ip() -> str:
|
||||
)
|
||||
|
||||
|
||||
# ── Health ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.get("/heartbeat")
|
||||
def heartbeat() -> tuple[Response, int]:
|
||||
return (
|
||||
@@ -63,11 +68,17 @@ def heartbeat() -> tuple[Response, int]:
|
||||
)
|
||||
|
||||
|
||||
# ── Pages ────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index() -> str:
|
||||
return render_template("index.html")
|
||||
|
||||
|
||||
# ── Stream endpoints ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/camera/start")
|
||||
def camera_start() -> tuple[Response, int]:
|
||||
status = CameraStatus.get()
|
||||
@@ -95,6 +106,7 @@ def camera_stop() -> tuple[Response, int]:
|
||||
@app.get("/camera/status")
|
||||
def camera_status() -> tuple[Response, int]:
|
||||
status = CameraStatus.get()
|
||||
rec = camera.recording_status()
|
||||
return (
|
||||
jsonify(
|
||||
{
|
||||
@@ -103,6 +115,8 @@ def camera_status() -> tuple[Response, int]:
|
||||
camera.wait_until_ready(timeout=0) if status.running else False
|
||||
),
|
||||
"updated_at": status.updated_at.isoformat(),
|
||||
"recording": rec["recording"],
|
||||
"recording_started_at": rec["started_at"],
|
||||
}
|
||||
),
|
||||
200,
|
||||
@@ -133,7 +147,6 @@ def hls_segment(filename: str) -> Response:
|
||||
if not hls_dir.exists():
|
||||
abort(404)
|
||||
|
||||
# set correct MIME types for HLS files
|
||||
if filename.endswith(".m3u8"):
|
||||
mimetype = "application/vnd.apple.mpegurl"
|
||||
elif filename.endswith(".ts"):
|
||||
@@ -144,5 +157,53 @@ def hls_segment(filename: str) -> Response:
|
||||
return send_from_directory(str(hls_dir), filename, mimetype=mimetype)
|
||||
|
||||
|
||||
# ── Recording endpoints ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@app.post("/camera/record/start")
|
||||
def record_start() -> tuple[Response, int]:
|
||||
if camera.recording:
|
||||
return jsonify({"status": "already_recording"}), 200
|
||||
|
||||
try:
|
||||
path = camera.start_recording()
|
||||
CameraRecordingEvent.log("record_start", get_client_ip(), str(path))
|
||||
return jsonify({"status": "recording", "path": str(path)}), 200
|
||||
except RuntimeError as exc:
|
||||
return jsonify({"status": "error", "message": str(exc)}), 409
|
||||
|
||||
|
||||
@app.post("/camera/record/stop")
|
||||
def record_stop() -> tuple[Response, int]:
|
||||
if not camera.recording:
|
||||
return jsonify({"status": "not_recording"}), 200
|
||||
|
||||
path = camera.stop_recording()
|
||||
filename = Path(path).name if path else None
|
||||
CameraRecordingEvent.log("record_stop", get_client_ip(), str(path))
|
||||
return jsonify({"status": "stopped", "filename": filename}), 200
|
||||
|
||||
|
||||
@app.get("/camera/recordings")
|
||||
def list_recordings() -> tuple[Response, int]:
|
||||
recordings = camera.list_recordings()
|
||||
return jsonify(recordings), 200
|
||||
|
||||
|
||||
@app.get("/camera/recordings/<filename>")
|
||||
def download_recording(filename: str) -> Response:
|
||||
# safety: only allow the expected filename pattern
|
||||
if not filename.startswith("recording_") or not filename.endswith(".mp4"):
|
||||
abort(404)
|
||||
if not RECORDINGS_DIR.exists():
|
||||
abort(404)
|
||||
return send_from_directory(
|
||||
str(RECORDINGS_DIR),
|
||||
filename,
|
||||
mimetype="video/mp4",
|
||||
as_attachment=True,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="0.0.0.0", port=5000, debug=True, threaded=True)
|
||||
|
||||
+194
-15
@@ -1,9 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -19,6 +22,7 @@ except ImportError:
|
||||
PICAMERA_AVAILABLE = False
|
||||
|
||||
HLS_DIR = Path("/tmp/hls")
|
||||
RECORDINGS_DIR = Path("/var/lib/birdcam/recordings")
|
||||
SEGMENT_DURATION = 2 # seconds per segment
|
||||
SEGMENT_COUNT = 5 # segments to keep in playlist
|
||||
BITRATE = 2_000_000 # 2 Mbps — adjust for bandwidth needs
|
||||
@@ -47,13 +51,21 @@ class Camera:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._picam: Picamera2 | None = None
|
||||
self._encoder: H264Encoder | None = None
|
||||
self._stream_encoder: H264Encoder | None = None
|
||||
self._record_encoder: H264Encoder | None = None
|
||||
self._ffmpeg: subprocess.Popen[bytes] | None = None
|
||||
self._record_ffmpeg: subprocess.Popen[bytes] | None = None
|
||||
self._output: PipeOutput | None = None
|
||||
self._ready_event = threading.Event()
|
||||
self._watch_thread: threading.Thread | None = None
|
||||
self._stop_event = threading.Event()
|
||||
self.running = False
|
||||
self.recording = False
|
||||
self._current_recording_path: Path | None = None
|
||||
self._recording_started_at: datetime | None = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# ── Streaming ────────────────────────────────────────────────────────────
|
||||
|
||||
def start(self) -> None:
|
||||
if self.running:
|
||||
@@ -65,7 +77,7 @@ class Camera:
|
||||
HLS_DIR.mkdir(parents=True)
|
||||
|
||||
if not PICAMERA_AVAILABLE:
|
||||
logger.info("Mock camera started")
|
||||
logger.info("Mock camera started (picamera2 not available)")
|
||||
self.running = True
|
||||
return
|
||||
|
||||
@@ -75,11 +87,11 @@ class Camera:
|
||||
"-loglevel",
|
||||
"warning",
|
||||
"-f",
|
||||
"h264", # input is raw H.264
|
||||
"h264",
|
||||
"-i",
|
||||
"pipe:0", # read from stdin
|
||||
"pipe:0",
|
||||
"-c:v",
|
||||
"copy", # no re-encoding — pass through directly
|
||||
"copy",
|
||||
"-hls_time",
|
||||
str(SEGMENT_DURATION),
|
||||
"-hls_list_size",
|
||||
@@ -99,7 +111,6 @@ class Camera:
|
||||
)
|
||||
self._output = PipeOutput(self._ffmpeg)
|
||||
|
||||
# configure picamera2 with H.264 encoder
|
||||
self._picam = Picamera2()
|
||||
config = self._picam.create_video_configuration(
|
||||
main={"size": (1280, 720)},
|
||||
@@ -107,19 +118,18 @@ class Camera:
|
||||
self._picam.configure(config)
|
||||
self._picam.set_controls(
|
||||
{
|
||||
"Brightness": 0.1, # -1.0 to 1.0, default 0.0
|
||||
"Contrast": 1.1, # 0.0 to 32.0, default 1.0
|
||||
"Saturation": 1.1, # 0.0 to 32.0, default 1.0
|
||||
"Sharpness": 1.0, # 0.0 to 16.0, default 1.0
|
||||
"AwbEnable": True, # auto white balance
|
||||
"AeEnable": True, # auto exposure
|
||||
"Brightness": 0.1,
|
||||
"Contrast": 1.1,
|
||||
"Saturation": 1.1,
|
||||
"Sharpness": 1.0,
|
||||
"AwbEnable": True,
|
||||
"AeEnable": True,
|
||||
}
|
||||
)
|
||||
self._encoder = H264Encoder(bitrate=BITRATE)
|
||||
self._stream_encoder = H264Encoder(bitrate=BITRATE)
|
||||
buffered = io.BufferedWriter(self._output)
|
||||
self._picam.start_recording(self._encoder, FileOutput(buffered))
|
||||
self._picam.start_recording(self._stream_encoder, FileOutput(buffered))
|
||||
|
||||
# watch for the playlist to appear — signals first segment is ready
|
||||
self._stop_event.clear()
|
||||
self._ready_event.clear()
|
||||
self._watch_thread = threading.Thread(target=self._watch_playlist, daemon=True)
|
||||
@@ -147,6 +157,11 @@ class Camera:
|
||||
def stop(self) -> None:
|
||||
if not self.running:
|
||||
return
|
||||
|
||||
# stop any active recording first
|
||||
if self.recording:
|
||||
self.stop_recording()
|
||||
|
||||
self._stop_event.set()
|
||||
self._ready_event.set() # unblock any waiters
|
||||
|
||||
@@ -174,6 +189,170 @@ class Camera:
|
||||
self.running = False
|
||||
logger.info("Camera stopped")
|
||||
|
||||
# ── Recording ────────────────────────────────────────────────────────────
|
||||
|
||||
def start_recording(self) -> Path:
|
||||
"""
|
||||
Start recording to an MP4 file. Can run independently of or
|
||||
simultaneously with the HLS stream. Returns the output file path.
|
||||
"""
|
||||
with self._lock:
|
||||
if self.recording:
|
||||
raise RuntimeError("Already recording")
|
||||
|
||||
RECORDINGS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S")
|
||||
output_path = RECORDINGS_DIR / f"recording_{timestamp}.mp4"
|
||||
|
||||
if not PICAMERA_AVAILABLE:
|
||||
# mock mode — create an empty placeholder file
|
||||
output_path.touch()
|
||||
self._current_recording_path = output_path
|
||||
self._recording_started_at = datetime.now(UTC)
|
||||
self.recording = True
|
||||
logger.info("Mock recording started: %s", output_path)
|
||||
return output_path
|
||||
|
||||
if self._picam is None:
|
||||
# camera not yet streaming — start it temporarily in record-only mode
|
||||
self._picam = Picamera2()
|
||||
config = self._picam.create_video_configuration(
|
||||
main={"size": (1280, 720)},
|
||||
)
|
||||
self._picam.configure(config)
|
||||
self._picam.set_controls(
|
||||
{
|
||||
"Brightness": 0.1,
|
||||
"Contrast": 1.1,
|
||||
"Saturation": 1.1,
|
||||
"Sharpness": 1.0,
|
||||
"AwbEnable": True,
|
||||
"AeEnable": True,
|
||||
}
|
||||
)
|
||||
|
||||
# pipe raw H.264 → ffmpeg → MP4 container
|
||||
ffmpeg_cmd = [
|
||||
"ffmpeg",
|
||||
"-loglevel",
|
||||
"warning",
|
||||
"-f",
|
||||
"h264",
|
||||
"-i",
|
||||
"pipe:0",
|
||||
"-c:v",
|
||||
"copy",
|
||||
"-movflags",
|
||||
"+faststart",
|
||||
str(output_path),
|
||||
]
|
||||
|
||||
self._record_ffmpeg = subprocess.Popen(
|
||||
ffmpeg_cmd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
|
||||
record_pipe = PipeOutput(self._record_ffmpeg)
|
||||
record_buffered = io.BufferedWriter(record_pipe)
|
||||
|
||||
self._record_encoder = H264Encoder(bitrate=BITRATE)
|
||||
|
||||
if self.running:
|
||||
# camera already running for streaming — add a second encoder output
|
||||
self._picam.start_encoder(
|
||||
self._record_encoder,
|
||||
FileOutput(record_buffered),
|
||||
)
|
||||
else:
|
||||
# no stream active — start camera just for recording
|
||||
self._picam.start_recording(
|
||||
self._record_encoder,
|
||||
FileOutput(record_buffered),
|
||||
)
|
||||
|
||||
self._current_recording_path = output_path
|
||||
self._recording_started_at = datetime.now(UTC)
|
||||
self.recording = True
|
||||
logger.info("Recording started: %s", output_path)
|
||||
return output_path
|
||||
|
||||
def stop_recording(self) -> Path | None:
|
||||
"""Stop the active recording and finalise the MP4 file."""
|
||||
with self._lock:
|
||||
if not self.recording:
|
||||
return None
|
||||
|
||||
path = self._current_recording_path
|
||||
|
||||
if PICAMERA_AVAILABLE and self._record_encoder is not None:
|
||||
if self.running:
|
||||
# streaming still active — only stop the recording encoder
|
||||
self._picam.stop_encoder(self._record_encoder) # type: ignore[union-attr]
|
||||
else:
|
||||
# recording-only mode — stop the whole camera
|
||||
if self._picam:
|
||||
self._picam.stop_recording()
|
||||
self._picam.close()
|
||||
self._picam = None
|
||||
|
||||
self._record_encoder = None
|
||||
|
||||
if self._record_ffmpeg:
|
||||
if self._record_ffmpeg.stdin:
|
||||
self._record_ffmpeg.stdin.close()
|
||||
try:
|
||||
self._record_ffmpeg.wait(timeout=10)
|
||||
except subprocess.TimeoutExpired:
|
||||
self._record_ffmpeg.kill()
|
||||
self._record_ffmpeg = None
|
||||
|
||||
self.recording = False
|
||||
self._current_recording_path = None
|
||||
self._recording_started_at = None
|
||||
logger.info("Recording stopped: %s", path)
|
||||
return path
|
||||
|
||||
def recording_status(self) -> dict[str, object]:
|
||||
"""Return current recording state for the API."""
|
||||
return {
|
||||
"recording": self.recording,
|
||||
"path": (
|
||||
str(self._current_recording_path)
|
||||
if self._current_recording_path
|
||||
else None
|
||||
),
|
||||
"started_at": (
|
||||
self._recording_started_at.isoformat()
|
||||
if self._recording_started_at
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
def list_recordings(self) -> list[dict[str, object]]:
|
||||
"""Return metadata for all saved recordings, newest first."""
|
||||
if not RECORDINGS_DIR.exists():
|
||||
return []
|
||||
|
||||
recordings = []
|
||||
for f in sorted(RECORDINGS_DIR.glob("recording_*.mp4"), reverse=True):
|
||||
stat = f.stat()
|
||||
recordings.append(
|
||||
{
|
||||
"filename": f.name,
|
||||
"size_bytes": stat.st_size,
|
||||
"created_at": datetime.fromtimestamp(
|
||||
stat.st_ctime, tz=UTC
|
||||
).isoformat(),
|
||||
"duration_seconds": None, # could be derived via ffprobe if needed
|
||||
}
|
||||
)
|
||||
return recordings
|
||||
|
||||
# ── Properties ───────────────────────────────────────────────────────────
|
||||
|
||||
@property
|
||||
def hls_dir(self) -> Path:
|
||||
return HLS_DIR
|
||||
|
||||
@@ -46,6 +46,7 @@ class CameraStatus(Base):
|
||||
|
||||
class CameraEvent(Base):
|
||||
__tablename__ = "camera_events"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
action: Mapped[str] = mapped_column(String(10), nullable=False) # 'start' | 'stop'
|
||||
ip_address: Mapped[str] = mapped_column(String(45), nullable=False)
|
||||
@@ -73,3 +74,44 @@ class CameraEvent(Base):
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
class CameraRecordingEvent(Base):
|
||||
"""Audit log for recording start/stop actions."""
|
||||
|
||||
__tablename__ = "camera_recording_events"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||
action: Mapped[str] = mapped_column(
|
||||
String(20), nullable=False
|
||||
) # 'record_start' | 'record_stop'
|
||||
ip_address: Mapped[str] = mapped_column(String(45), nullable=False)
|
||||
file_path: Mapped[str] = mapped_column(String(512), nullable=False, default="")
|
||||
timestamp: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=lambda: datetime.now(UTC),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def log(action: str, ip_address: str, file_path: str = "") -> CameraRecordingEvent:
|
||||
event = CameraRecordingEvent(
|
||||
action=action,
|
||||
ip_address=ip_address,
|
||||
file_path=file_path,
|
||||
)
|
||||
db.session.add(event)
|
||||
db.session.commit()
|
||||
return event
|
||||
|
||||
@staticmethod
|
||||
def recent(limit: int = 50) -> list[CameraRecordingEvent]:
|
||||
return list(
|
||||
db.session.execute(
|
||||
db.select(CameraRecordingEvent)
|
||||
.order_by(CameraRecordingEvent.timestamp.desc())
|
||||
.limit(limit)
|
||||
)
|
||||
.scalars()
|
||||
.all()
|
||||
)
|
||||
|
||||
+262
-41
@@ -44,6 +44,24 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#preview.is-recording {
|
||||
outline: 2px solid #ef4444;
|
||||
outline-offset: 2px;
|
||||
animation: rec-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes rec-pulse {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
outline-color: #ef4444;
|
||||
}
|
||||
|
||||
50% {
|
||||
outline-color: #7f1d1d;
|
||||
}
|
||||
}
|
||||
|
||||
#stream-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -54,6 +72,10 @@
|
||||
#placeholder {
|
||||
color: #555;
|
||||
font-size: 0.9rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -70,7 +92,7 @@
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
transition: opacity 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
@@ -88,11 +110,128 @@
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#btn-record {
|
||||
background: #3f3f3f;
|
||||
color: #f0f0f0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#btn-record.is-recording {
|
||||
background: #b91c1c;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.rec-dot {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#btn-record.is-recording .rec-dot {
|
||||
animation: blink 1s step-start infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.8rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 999px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
|
||||
.badge.live {
|
||||
background: #dc2626;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
|
||||
#recordings-section {
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
#recordings-section h2 {
|
||||
font-size: 0.9rem;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: #666;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.4rem;
|
||||
border-bottom: 1px solid #222;
|
||||
}
|
||||
|
||||
#recordings-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.recording-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.recording-item .rec-name {
|
||||
font-family: monospace;
|
||||
color: #ccc;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.recording-item .rec-meta {
|
||||
color: #555;
|
||||
white-space: nowrap;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.recording-item a {
|
||||
color: #22c55e;
|
||||
text-decoration: none;
|
||||
font-size: 0.78rem;
|
||||
white-space: nowrap;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.recording-item a:hover {
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
#recordings-empty {
|
||||
color: #444;
|
||||
font-size: 0.82rem;
|
||||
text-align: center;
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -101,71 +240,165 @@
|
||||
|
||||
<div id="preview">
|
||||
<div id="placeholder">
|
||||
<div class="dot" id="status-dot"></div>
|
||||
<span id="placeholder-text">Checking stream...</span>
|
||||
</div>
|
||||
<video id="stream-video" autoplay muted playsinline></video>
|
||||
</div>
|
||||
|
||||
<div class="controls" id="controls"></div>
|
||||
|
||||
<p id="status"></p>
|
||||
|
||||
<!-- hls.js from CDN -->
|
||||
<div id="recordings-section">
|
||||
<h2>Recordings</h2>
|
||||
<div id="recordings-list">
|
||||
<p id="recordings-empty">No recordings yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js"></script>
|
||||
<script>
|
||||
const video = document.getElementById("stream-video");
|
||||
const placeholder = document.getElementById("placeholder");
|
||||
const placeholderText = document.getElementById("placeholder-text");
|
||||
const statusDot = document.getElementById("status-dot");
|
||||
const controls = document.getElementById("controls");
|
||||
const statusEl = document.getElementById("status");
|
||||
const preview = document.getElementById("preview");
|
||||
const recordingsList = document.getElementById("recordings-list");
|
||||
|
||||
let hls = null;
|
||||
let pollInterval = null;
|
||||
let isRecording = false;
|
||||
|
||||
function setStatus(msg) { statusEl.textContent = msg; }
|
||||
|
||||
function fmtBytes(b) {
|
||||
if (b < 1024) return b + " B";
|
||||
if (b < 1048576) return (b / 1024).toFixed(1) + " KB";
|
||||
return (b / 1048576).toFixed(1) + " MB";
|
||||
}
|
||||
|
||||
function fmtDate(iso) { return new Date(iso).toLocaleString(); }
|
||||
|
||||
// ── Recordings list ─────────────────────────────────────────────────────
|
||||
async function refreshRecordings() {
|
||||
const res = await fetch("/camera/recordings");
|
||||
const list = await res.json();
|
||||
recordingsList.innerHTML = "";
|
||||
|
||||
if (list.length === 0) {
|
||||
recordingsList.innerHTML = '<p id="recordings-empty">No recordings yet.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.forEach(rec => {
|
||||
const item = document.createElement("div");
|
||||
item.className = "recording-item";
|
||||
item.innerHTML =
|
||||
'<span class="rec-name">' + rec.filename + '</span>' +
|
||||
'<span class="rec-meta">' + fmtBytes(rec.size_bytes) + ' · ' + fmtDate(rec.created_at) + '</span>' +
|
||||
'<a href="/camera/recordings/' + rec.filename + '" download>Download</a>';
|
||||
recordingsList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Record button ───────────────────────────────────────────────────────
|
||||
function createRecordButton() {
|
||||
const btn = document.createElement("button");
|
||||
btn.id = "btn-record";
|
||||
const dot = document.createElement("span");
|
||||
dot.className = "rec-dot";
|
||||
const label = document.createElement("span");
|
||||
label.className = "rec-label";
|
||||
|
||||
if (isRecording) {
|
||||
btn.classList.add("is-recording");
|
||||
label.textContent = "Stop Recording";
|
||||
} else {
|
||||
label.textContent = "Record";
|
||||
}
|
||||
|
||||
btn.appendChild(dot);
|
||||
btn.appendChild(label);
|
||||
btn.addEventListener("click", toggleRecording);
|
||||
return btn;
|
||||
}
|
||||
|
||||
async function toggleRecording() {
|
||||
const btn = document.getElementById("btn-record");
|
||||
if (btn) btn.disabled = true;
|
||||
|
||||
if (!isRecording) {
|
||||
setStatus("Starting recording...");
|
||||
const res = await fetch("/camera/record/start", { method: "POST" });
|
||||
const data = await res.json();
|
||||
if (data.status === "recording" || data.status === "already_recording") {
|
||||
isRecording = true;
|
||||
setStatus("Recording");
|
||||
} else {
|
||||
setStatus("Failed to start recording: " + (data.message || "unknown error"));
|
||||
}
|
||||
} else {
|
||||
setStatus("Stopping recording...");
|
||||
await fetch("/camera/record/stop", { method: "POST" });
|
||||
isRecording = false;
|
||||
setStatus("Recording saved");
|
||||
await refreshRecordings();
|
||||
}
|
||||
|
||||
const streaming = !!document.getElementById("btn-stop");
|
||||
renderControls(streaming);
|
||||
}
|
||||
|
||||
// ── Controls ────────────────────────────────────────────────────────────
|
||||
function renderControls(running) {
|
||||
controls.innerHTML = "";
|
||||
|
||||
if (running) {
|
||||
const stop = document.createElement("button");
|
||||
stop.id = "btn-stop";
|
||||
stop.textContent = "Stop";
|
||||
stop.textContent = "Stop Stream";
|
||||
stop.addEventListener("click", stopStream);
|
||||
controls.appendChild(stop);
|
||||
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "badge live";
|
||||
badge.textContent = "● Live";
|
||||
controls.appendChild(badge);
|
||||
} else {
|
||||
const start = document.createElement("button");
|
||||
start.id = "btn-start";
|
||||
start.textContent = "Start";
|
||||
start.textContent = "Start Stream";
|
||||
start.addEventListener("click", startStream);
|
||||
controls.appendChild(start);
|
||||
}
|
||||
|
||||
controls.appendChild(createRecordButton());
|
||||
|
||||
if (running) {
|
||||
const badge = document.createElement("span");
|
||||
badge.className = "badge live";
|
||||
badge.textContent = "Live";
|
||||
controls.appendChild(badge);
|
||||
}
|
||||
|
||||
function showOffline(message = "Stream is offline") {
|
||||
if (isRecording) {
|
||||
preview.classList.add("is-recording");
|
||||
} else {
|
||||
preview.classList.remove("is-recording");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Video display ────────────────────────────────────────────────────────
|
||||
function showOffline(message) {
|
||||
video.style.display = "none";
|
||||
video.src = "";
|
||||
placeholder.style.display = "flex";
|
||||
placeholderText.textContent = message;
|
||||
statusDot.classList.remove("live");
|
||||
placeholderText.textContent = message || "Stream is offline";
|
||||
}
|
||||
|
||||
function showLive() {
|
||||
placeholder.style.display = "none";
|
||||
video.style.display = "block";
|
||||
statusDot.classList.add("live");
|
||||
}
|
||||
|
||||
// ── HLS ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── HLS ──────────────────────────────────────────────────────────────────
|
||||
function startHls() {
|
||||
const src = "/camera/hls/stream.m3u8";
|
||||
|
||||
if (hls) { hls.destroy(); hls = null; }
|
||||
|
||||
if (Hls.isSupported()) {
|
||||
@@ -179,7 +412,6 @@
|
||||
});
|
||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||
if (data.fatal) {
|
||||
console.error("HLS fatal error:", data);
|
||||
showOffline("Stream error — reloading...");
|
||||
setTimeout(attachToStream, 3000);
|
||||
}
|
||||
@@ -201,18 +433,14 @@
|
||||
video.style.display = "none";
|
||||
}
|
||||
|
||||
// ── Status polling ───────────────────────────────────────────────────────
|
||||
|
||||
// ── Status polling ────────────────────────────────────────────────────────
|
||||
async function fetchStatus() {
|
||||
const res = await fetch("/camera/status");
|
||||
return res.json();
|
||||
}
|
||||
|
||||
function setStatus(msg) {
|
||||
statusEl.textContent = msg;
|
||||
}
|
||||
|
||||
async function waitForReady(maxAttempts = 40) {
|
||||
async function waitForReady(maxAttempts) {
|
||||
maxAttempts = maxAttempts || 40;
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const data = await fetchStatus();
|
||||
if (data.ready) return true;
|
||||
@@ -221,9 +449,10 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
// Called on page load and after stream errors — attach if already live
|
||||
async function attachToStream() {
|
||||
const data = await fetchStatus();
|
||||
isRecording = data.recording || false;
|
||||
|
||||
if (data.ready) {
|
||||
startHls();
|
||||
renderControls(true);
|
||||
@@ -259,15 +488,10 @@
|
||||
if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
|
||||
}
|
||||
|
||||
// ── Stream start / stop ──────────────────────────────────────────────────
|
||||
|
||||
// ── Stream start / stop ───────────────────────────────────────────────────
|
||||
async function startStream() {
|
||||
// guard: re-check status in case another client just started it
|
||||
const current = await fetchStatus();
|
||||
if (current.running || current.ready) {
|
||||
await attachToStream();
|
||||
return;
|
||||
}
|
||||
if (current.running || current.ready) { await attachToStream(); return; }
|
||||
|
||||
const btn = document.getElementById("btn-start");
|
||||
if (btn) btn.disabled = true;
|
||||
@@ -275,7 +499,6 @@
|
||||
placeholderText.textContent = "Starting...";
|
||||
|
||||
await fetch("/camera/start", { method: "POST" });
|
||||
|
||||
setStatus("Waiting for first segment...");
|
||||
const ready = await waitForReady();
|
||||
|
||||
@@ -301,14 +524,12 @@
|
||||
startOfflinePoll();
|
||||
}
|
||||
|
||||
// ── Init ────────────────────────────────────────────────────────────────
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────────
|
||||
(async () => {
|
||||
await attachToStream();
|
||||
await refreshRecordings();
|
||||
const data = await fetchStatus();
|
||||
if (!data.running && !data.ready) {
|
||||
startOfflinePoll();
|
||||
}
|
||||
if (!data.running && !data.ready) startOfflinePoll();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+93
-2
@@ -17,6 +17,9 @@ def client() -> Generator[FlaskClient, Any, Any]:
|
||||
db.drop_all()
|
||||
|
||||
|
||||
# ── Stream tests ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_heartbeat_status_code(client: FlaskClient) -> None:
|
||||
response = client.get("/heartbeat")
|
||||
assert response.status_code == 200
|
||||
@@ -35,7 +38,7 @@ def test_camera_stop(client: FlaskClient) -> None:
|
||||
assert response.get_json()["status"] in ("stopped", "already_stopped")
|
||||
|
||||
|
||||
def test_double_start_is_idempotent(client):
|
||||
def test_double_start_is_idempotent(client: FlaskClient) -> None:
|
||||
client.post("/camera/start")
|
||||
res = client.post("/camera/start")
|
||||
assert res.get_json()["status"] == "already_running"
|
||||
@@ -48,9 +51,19 @@ def test_camera_status(client: FlaskClient) -> None:
|
||||
assert "running" in data
|
||||
assert "ready" in data
|
||||
assert "updated_at" in data
|
||||
# recording fields must always be present
|
||||
assert "recording" in data
|
||||
assert "recording_started_at" in data
|
||||
|
||||
|
||||
def test_camera_log(client):
|
||||
def test_camera_status_includes_recording_false_by_default(client: FlaskClient) -> None:
|
||||
res = client.get("/camera/status")
|
||||
data = res.get_json()
|
||||
assert data["recording"] is False
|
||||
assert data["recording_started_at"] is None
|
||||
|
||||
|
||||
def test_camera_log(client: FlaskClient) -> None:
|
||||
client.post("/camera/start")
|
||||
client.post("/camera/stop")
|
||||
res = client.get("/camera/log")
|
||||
@@ -72,3 +85,81 @@ def test_index_page(client: FlaskClient) -> None:
|
||||
response = client.get("/")
|
||||
assert response.status_code == 200
|
||||
assert b"Pi Camera" in response.data
|
||||
|
||||
|
||||
# ── Recording tests ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_record_start_returns_200(client: FlaskClient) -> None:
|
||||
res = client.post("/camera/record/start")
|
||||
assert res.status_code == 200
|
||||
data = res.get_json()
|
||||
assert data["status"] in ("recording", "already_recording")
|
||||
|
||||
|
||||
def test_record_stop_when_not_recording(client: FlaskClient) -> None:
|
||||
res = client.post("/camera/record/stop")
|
||||
assert res.status_code == 200
|
||||
assert res.get_json()["status"] == "not_recording"
|
||||
|
||||
|
||||
def test_record_start_then_stop(client: FlaskClient) -> None:
|
||||
start = client.post("/camera/record/start")
|
||||
assert start.get_json()["status"] == "recording"
|
||||
|
||||
stop = client.post("/camera/record/stop")
|
||||
assert stop.status_code == 200
|
||||
assert stop.get_json()["status"] == "stopped"
|
||||
|
||||
|
||||
def test_double_record_start_is_idempotent(client: FlaskClient) -> None:
|
||||
client.post("/camera/record/start")
|
||||
res = client.post("/camera/record/start")
|
||||
assert res.get_json()["status"] == "already_recording"
|
||||
# clean up
|
||||
client.post("/camera/record/stop")
|
||||
|
||||
|
||||
def test_status_reflects_recording_state(client: FlaskClient) -> None:
|
||||
client.post("/camera/record/start")
|
||||
status = client.get("/camera/status").get_json()
|
||||
assert status["recording"] is True
|
||||
assert status["recording_started_at"] is not None
|
||||
|
||||
client.post("/camera/record/stop")
|
||||
status = client.get("/camera/status").get_json()
|
||||
assert status["recording"] is False
|
||||
assert status["recording_started_at"] is None
|
||||
|
||||
|
||||
def test_stream_and_record_simultaneously(client: FlaskClient) -> None:
|
||||
"""Stream and record can be active at the same time without interference."""
|
||||
client.post("/camera/start")
|
||||
rec = client.post("/camera/record/start")
|
||||
assert rec.get_json()["status"] in ("recording", "already_recording")
|
||||
|
||||
status = client.get("/camera/status").get_json()
|
||||
assert status["running"] is True
|
||||
assert status["recording"] is True
|
||||
|
||||
# stopping stream does not affect recording state report from camera object
|
||||
client.post("/camera/stop")
|
||||
# recording was stopped as part of camera.stop() — that is expected behaviour
|
||||
# (the camera itself cleans up on stop)
|
||||
|
||||
|
||||
def test_list_recordings_empty(client: FlaskClient) -> None:
|
||||
res = client.get("/camera/recordings")
|
||||
assert res.status_code == 200
|
||||
assert res.get_json() == []
|
||||
|
||||
|
||||
def test_download_recording_invalid_filename(client: FlaskClient) -> None:
|
||||
# filename pattern not matching should 404
|
||||
res = client.get("/camera/recordings/../../etc/passwd")
|
||||
assert res.status_code in (404, 400)
|
||||
|
||||
|
||||
def test_download_recording_wrong_prefix(client: FlaskClient) -> None:
|
||||
res = client.get("/camera/recordings/evil.mp4")
|
||||
assert res.status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user