From 154ba6d71db93dac46b7ab158e25ea5e93ea1d50 Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:06:20 -0700 Subject: [PATCH 01/10] Update schema to hold multiple olfactometers --- schema/aind_behavior_device_olfactometer.json | 18 +- ...indBehaviorDeviceOlfactometer.Generated.cs | 33 +- src/aind_behavior_device_olfactometer/rig.py | 4 + uv.lock | 476 ++++++------------ 4 files changed, 192 insertions(+), 339 deletions(-) diff --git a/schema/aind_behavior_device_olfactometer.json b/schema/aind_behavior_device_olfactometer.json index 114a46b..5acdb32 100644 --- a/schema/aind_behavior_device_olfactometer.json +++ b/schema/aind_behavior_device_olfactometer.json @@ -675,7 +675,7 @@ "title": "Rng Seed" }, "aind_behavior_services_pkg_version": { - "default": "0.13.6", + "default": "0.13.7", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "title": "aind_behavior_services package version", "type": "string" @@ -727,7 +727,7 @@ "OlfactometerCalibrationRig": { "properties": { "aind_behavior_services_pkg_version": { - "default": "0.13.6", + "default": "0.13.7", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "title": "aind_behavior_services package version", "type": "string" @@ -758,6 +758,14 @@ "$ref": "#/$defs/Olfactometer", "title": "Olfactometer device" }, + "harp_olfactometer_extension": { + "description": "A collection of subordinate olfactometers that can be added to increase the number of independently delivered odors. The order of the list determines the order by which odors are numbered", + "items": { + "$ref": "#/$defs/Olfactometer" + }, + "title": "Harp Olfactometer Extension", + "type": "array" + }, "harp_analog_input": { "$ref": "#/$defs/HarpAnalogInput", "title": "Analog input device" @@ -882,14 +890,14 @@ "Session": { "properties": { "aind_behavior_services_pkg_version": { - "default": "0.13.6", + "default": "0.13.7", "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", "title": "aind_behavior_services package version", "type": "string" }, "version": { - "const": "0.13.6", - "default": "0.13.6", + "const": "0.13.7", + "default": "0.13.7", "title": "Version", "type": "string" }, diff --git a/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs b/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs index cd773da..9c73596 100644 --- a/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs +++ b/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs @@ -1006,7 +1006,7 @@ public partial class OlfactometerCalibrationParameters public OlfactometerCalibrationParameters() { - _aindBehaviorServicesPkgVersion = "0.13.6"; + _aindBehaviorServicesPkgVersion = "0.13.7"; _fullFlowRate = 1000D; _nRepeatsPerStimulus = 1; _timeOn = 1D; @@ -1195,6 +1195,8 @@ public partial class OlfactometerCalibrationRig private Olfactometer _harpOlfactometer; + private System.Collections.Generic.List _harpOlfactometerExtension; + private HarpAnalogInput _harpAnalogInput; private HarpWhiteRabbit _harpClockGenerator; @@ -1205,9 +1207,10 @@ public partial class OlfactometerCalibrationRig public OlfactometerCalibrationRig() { - _aindBehaviorServicesPkgVersion = "0.13.6"; + _aindBehaviorServicesPkgVersion = "0.13.7"; _version = "0.2.0-rc1"; _harpOlfactometer = new Olfactometer(); + _harpOlfactometerExtension = new System.Collections.Generic.List(); _harpAnalogInput = new HarpAnalogInput(); _harpClockGenerator = new HarpWhiteRabbit(); _harpManipulator = new AllenNeuralDynamics.AindManipulator.AindManipulator(); @@ -1221,6 +1224,7 @@ protected OlfactometerCalibrationRig(OlfactometerCalibrationRig other) _rigName = other._rigName; _dataDirectory = other._dataDirectory; _harpOlfactometer = other._harpOlfactometer; + _harpOlfactometerExtension = other._harpOlfactometerExtension; _harpAnalogInput = other._harpAnalogInput; _harpClockGenerator = other._harpClockGenerator; _harpManipulator = other._harpManipulator; @@ -1318,6 +1322,26 @@ public Olfactometer HarpOlfactometer } } + /// + /// A collection of subordinate olfactometers that can be added to increase the number of independently delivered odors. The order of the list determines the order by which odors are numbered + /// + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("harp_olfactometer_extension")] + [System.ComponentModel.DescriptionAttribute("A collection of subordinate olfactometers that can be added to increase the numbe" + + "r of independently delivered odors. The order of the list determines the order b" + + "y which odors are numbered")] + public System.Collections.Generic.List HarpOlfactometerExtension + { + get + { + return _harpOlfactometerExtension; + } + set + { + _harpOlfactometerExtension = value; + } + } + [System.Xml.Serialization.XmlIgnoreAttribute()] [Newtonsoft.Json.JsonPropertyAttribute("harp_analog_input", Required=Newtonsoft.Json.Required.Always)] public HarpAnalogInput HarpAnalogInput @@ -1400,6 +1424,7 @@ protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) stringBuilder.Append("RigName = " + _rigName + ", "); stringBuilder.Append("DataDirectory = " + _dataDirectory + ", "); stringBuilder.Append("HarpOlfactometer = " + _harpOlfactometer + ", "); + stringBuilder.Append("HarpOlfactometerExtension = " + _harpOlfactometerExtension + ", "); stringBuilder.Append("HarpAnalogInput = " + _harpAnalogInput + ", "); stringBuilder.Append("HarpClockGenerator = " + _harpClockGenerator + ", "); stringBuilder.Append("HarpManipulator = " + _harpManipulator + ", "); @@ -1642,8 +1667,8 @@ public partial class Session public Session() { - _aindBehaviorServicesPkgVersion = "0.13.6"; - _version = "0.13.6"; + _aindBehaviorServicesPkgVersion = "0.13.7"; + _version = "0.13.7"; _experimenter = new System.Collections.Generic.List(); _allowDirtyRepo = false; _skipHardwareValidation = false; diff --git a/src/aind_behavior_device_olfactometer/rig.py b/src/aind_behavior_device_olfactometer/rig.py index b8e1542..730937a 100644 --- a/src/aind_behavior_device_olfactometer/rig.py +++ b/src/aind_behavior_device_olfactometer/rig.py @@ -19,6 +19,10 @@ class AlicatFlowmeter(rig.Device): class OlfactometerCalibrationRig(rig.Rig): version: Literal[__semver__] = __semver__ harp_olfactometer: olf.Olfactometer = Field(title="Olfactometer device") + harp_olfactometer_extension: list[olf.Olfactometer] = Field( + default_factory=list, + description="A collection of subordinate olfactometers that can be added to increase the number of independently delivered odors. The order of the list determines the order by which odors are numbered", + ) harp_analog_input: harp.HarpAnalogInput = Field(title="Analog input device") harp_clock_generator: harp.HarpWhiteRabbit = Field(title="Clock generator device") harp_manipulator: man.AindManipulator = Field(description="Manipulator") diff --git a/uv.lock b/uv.lock index ee4eb00..2699c01 100644 --- a/uv.lock +++ b/uv.lock @@ -108,7 +108,7 @@ docs = [ [[package]] name = "aind-behavior-services" -version = "0.13.6" +version = "0.13.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aind-behavior-curriculum" }, @@ -117,14 +117,14 @@ dependencies = [ { name = "pydantic" }, { name = "semver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1d/ed/ccf529e8e53a3a22f956cb30a36e53c127b3aeb9ff31f63438d458696d4e/aind_behavior_services-0.13.6.tar.gz", hash = "sha256:aa37ae991e07c454a0c4fbc8b977f22ae81dd04f2a8acabeb13d587b187b6494", size = 29469, upload-time = "2026-03-31T15:46:30.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/6d/540d7221485fb04ae4f04f99a57c930035c39ecb2b899937828aae81936f/aind_behavior_services-0.13.7.tar.gz", hash = "sha256:2ef5a28ea5918c059cf4444e105ce00068d5285193eb63bf7f87b89db2d1b5d2", size = 29573, upload-time = "2026-04-18T22:20:04.267Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/71/26/5645935312184f8d1bf3982f2d114247c43c461577caa49a16099f3679ee/aind_behavior_services-0.13.6-py3-none-any.whl", hash = "sha256:c0d28959a39c152d9ddbf290b3b3c87b8ad6fa1d5044d81d3507c6a167e45e26", size = 39516, upload-time = "2026-03-31T15:46:29.809Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bf/423d28364d564008a33d40d7aad1fbbed5e028b5bda39ed5cafc41651d7e/aind_behavior_services-0.13.7-py3-none-any.whl", hash = "sha256:ab57927ce283cb07a47c4b02eecee8038421f8292c5efbeeaaf907fd6a69eb08", size = 39614, upload-time = "2026-04-18T22:20:02.941Z" }, ] [[package]] name = "aind-clabe" -version = "0.10.0" +version = "0.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aind-behavior-services" }, @@ -136,9 +136,9 @@ dependencies = [ { name = "rich" }, { name = "semver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/e1/adad7bfdaa645adedcc675b42e21bb79d91bf04a41fe8ca5f639f10bc05a/aind_clabe-0.10.0.tar.gz", hash = "sha256:0d4331c25c1842b07fbabbb9a9724e393ff2eb7f3ee91ca30fab7af12993fc0b", size = 70824, upload-time = "2026-01-29T17:41:10.852Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/d3/4ec628e759b31bae8470f1b2f69cc6b1793d2e96c40a80b9fd8402132390/aind_clabe-0.10.2.tar.gz", hash = "sha256:13062f37c440382e81390ea26330842e671630dcb9bd5a590772b0293041b554", size = 69654, upload-time = "2026-04-17T22:41:36.864Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/8a/4389a0ef7e8958a5a8001ef5f7f00ea121ad0feda92571c0947e85a99d58/aind_clabe-0.10.0-py3-none-any.whl", hash = "sha256:dabe20924c236303613a31c4166579f838441060958029e8dca530aa2d3056ad", size = 94256, upload-time = "2026-01-29T17:41:12.205Z" }, + { url = "https://files.pythonhosted.org/packages/48/41/2555fac0bd26b3179e609cb50d39db08fce9538b051c35ac6ae4a4fcb6f2/aind_clabe-0.10.2-py3-none-any.whl", hash = "sha256:a6f04b3577e5d73e21c4c34538c20271815dc3a5496888d8c8219ee2f69128c2", size = 92819, upload-time = "2026-04-17T22:41:35.431Z" }, ] [package.optional-dependencies] @@ -146,14 +146,9 @@ aind-services = [ { name = "aind-data-schema" }, { name = "aind-data-transfer-service" }, { name = "aind-watchdog-service" }, - { name = "cryptography" }, - { name = "ldap3", marker = "sys_platform == 'win32'" }, - { name = "ms-active-directory" }, - { name = "msal", marker = "sys_platform == 'win32'" }, { name = "pykeepass" }, { name = "pyyaml" }, { name = "requests" }, - { name = "winkerberos", marker = "sys_platform == 'win32'" }, ] [[package]] @@ -172,16 +167,16 @@ wheels = [ [[package]] name = "aind-data-schema-models" -version = "5.4.2" +version = "5.4.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-resources" }, { name = "pydantic" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/6e/f98fde74e466b736c46e5f71bf28e4dad130798d6048902acb8ac44fae60/aind_data_schema_models-5.4.2.tar.gz", hash = "sha256:e8a1a3fca2a13fb909f7c7915d2f03faf83b2c5e6567ac7721fe1f8f29482571", size = 341115, upload-time = "2026-03-26T01:29:56.322Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/72/7271bcf1053b771a9aa832a2026a9b7bb9ebbe9f618297dd21b93044e749/aind_data_schema_models-5.4.5.tar.gz", hash = "sha256:cb91300ed33f77f6ccb30c28bf1085a6ec856f0c99afa47cf66d48d918dc07de", size = 341353, upload-time = "2026-04-20T22:58:37.536Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/fb/0ec99d2b17bc128908524bcf37c2df6b39134717d3c18fa65ad656bcb91e/aind_data_schema_models-5.4.2-py3-none-any.whl", hash = "sha256:4c11c390d993e48ba6308f5dfc7a426344f8436cf7d80f1c08641b040ad69b1e", size = 309923, upload-time = "2026-03-26T01:29:55.064Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5d/892c2d9f94195828a11981ef2be2c5b05f49ff3d4cc93f79af6e67e5a716/aind_data_schema_models-5.4.5-py3-none-any.whl", hash = "sha256:f61275eb4145c3fb1e65374978f79e85a021a7e0dc9f05156c883037b0211660", size = 310151, upload-time = "2026-04-20T22:58:36.235Z" }, ] [[package]] @@ -725,65 +720,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/9a/94fa67d2aecf7703a68be18b97b8d1af09ec9c26c73b2c21d5e881e46353/contraqctor-0.5.3-py3-none-any.whl", hash = "sha256:9306521efd6165007409319be39d4a8d9e286028693237584dbfc9f87e974292", size = 67638, upload-time = "2025-11-10T17:31:18.482Z" }, ] -[[package]] -name = "cryptography" -version = "46.0.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, - { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, - { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, - { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, - { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, - { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, - { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, - { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, - { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, - { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, - { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, - { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, - { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, - { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, - { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, - { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, - { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, - { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, - { url = "https://files.pythonhosted.org/packages/63/0c/dca8abb64e7ca4f6b2978769f6fea5ad06686a190cec381f0a796fdcaaba/cryptography-46.0.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:fc9ab8856ae6cf7c9358430e49b368f3108f050031442eaeb6b9d87e4dcf4e4f", size = 3476879, upload-time = "2026-04-08T01:57:38.664Z" }, - { url = "https://files.pythonhosted.org/packages/3a/ea/075aac6a84b7c271578d81a2f9968acb6e273002408729f2ddff517fed4a/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d3b99c535a9de0adced13d159c5a9cf65c325601aa30f4be08afd680643e9c15", size = 4219700, upload-time = "2026-04-08T01:57:40.625Z" }, - { url = "https://files.pythonhosted.org/packages/6c/7b/1c55db7242b5e5612b29fc7a630e91ee7a6e3c8e7bf5406d22e206875fbd/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d02c738dacda7dc2a74d1b2b3177042009d5cab7c7079db74afc19e56ca1b455", size = 4385982, upload-time = "2026-04-08T01:57:42.725Z" }, - { url = "https://files.pythonhosted.org/packages/cb/da/9870eec4b69c63ef5925bf7d8342b7e13bc2ee3d47791461c4e49ca212f4/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:04959522f938493042d595a736e7dbdff6eb6cc2339c11465b3ff89343b65f65", size = 4219115, upload-time = "2026-04-08T01:57:44.939Z" }, - { url = "https://files.pythonhosted.org/packages/f4/72/05aa5832b82dd341969e9a734d1812a6aadb088d9eb6f0430fc337cc5a8f/cryptography-46.0.7-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:3986ac1dee6def53797289999eabe84798ad7817f3e97779b5061a95b0ee4968", size = 4385479, upload-time = "2026-04-08T01:57:46.86Z" }, - { url = "https://files.pythonhosted.org/packages/20/2a/1b016902351a523aa2bd446b50a5bc1175d7a7d1cf90fe2ef904f9b84ebc/cryptography-46.0.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:258514877e15963bd43b558917bc9f54cf7cf866c38aa576ebf47a77ddbc43a4", size = 3412829, upload-time = "2026-04-08T01:57:48.874Z" }, -] - [[package]] name = "cycler" version = "0.12.1" @@ -1071,11 +1007,11 @@ wheels = [ [[package]] name = "importlib-resources" -version = "6.5.2" +version = "7.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/06/b56dfa750b44e86157093bc8fca0ab81dccbf5260510de4eaf1cb69b5b99/importlib_resources-7.1.0.tar.gz", hash = "sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708", size = 44985, upload-time = "2026-04-12T16:36:09.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, + { url = "https://files.pythonhosted.org/packages/8a/db/55a262f3606bebcae07cc14095338471ad7c0bbcaa37707e6f0ee49725b7/importlib_resources-7.1.0-py3-none-any.whl", hash = "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1", size = 37232, upload-time = "2026-04-12T16:36:08.219Z" }, ] [[package]] @@ -1597,118 +1533,106 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, ] -[[package]] -name = "ldap3" -version = "2.9.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyasn1" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/ac/96bd5464e3edbc61595d0d69989f5d9969ae411866427b2500a8e5b812c0/ldap3-2.9.1.tar.gz", hash = "sha256:f3e7fc4718e3f09dda568b57100095e0ce58633bcabbed8667ce3f8fbaa4229f", size = 398830, upload-time = "2021-07-18T06:34:21.786Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/f6/71d6ec9f18da0b2201287ce9db6afb1a1f637dedb3f0703409558981c723/ldap3-2.9.1-py2.py3-none-any.whl", hash = "sha256:5869596fc4948797020d3f03b7939da938778a0f9e2009f7a072ccf92b8e8d70", size = 432192, upload-time = "2021-07-18T06:34:12.905Z" }, -] - [[package]] name = "lxml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/42/149c7747977db9d68faee960c1a3391eb25e94d4bb677f8e2df8328e4098/lxml-6.0.3.tar.gz", hash = "sha256:a1664c5139755df44cab3834f4400b331b02205d62d3fdcb1554f63439bf3372", size = 4237567, upload-time = "2026-04-09T14:39:09.664Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/ce/8a0b4747bb5dd47fec3443f0506a2a2d4f58946d7176bc3fdcae781ac666/lxml-6.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c8184fdb2259bda1db2db9d6e25f667769afc2531830b4fa29f83f66a7872dea", size = 8524445, upload-time = "2026-04-09T14:34:14.244Z" }, - { url = "https://files.pythonhosted.org/packages/e5/14/b74a06da69d212d1ac27e4bcf124e966d1d63c4d23522add86fbcf20324e/lxml-6.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b0f01fb8bdcaf4aa69cf55b2b2f8ef722e4423e1c020e7250dcb89a1d5db38e", size = 4594891, upload-time = "2026-04-09T14:34:17.123Z" }, - { url = "https://files.pythonhosted.org/packages/39/9a/364392e9740ddcdba380c5dbb79464956aadf81135344d57153631c8e4a2/lxml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fab00cef83d4f9d76c5e0722346e84bc174b071d68b4f725aeb0bf3877b9e6a6", size = 4922596, upload-time = "2026-04-09T14:34:19.632Z" }, - { url = "https://files.pythonhosted.org/packages/24/b6/6e4a53869a8e031dc5ea564a9857f6dd520a05412aea8d1b6565e8b2d43d/lxml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f753db5785ce019d7b25bb75638ef5a42a0e208aa9f19933262134e668ca6af", size = 5067033, upload-time = "2026-04-09T14:34:22.015Z" }, - { url = "https://files.pythonhosted.org/packages/2d/cc/12035c0d104fbe64e56e7b2cd9d4942ffa2a1689f093f44de0eef73538ae/lxml-6.0.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27e317e554bc6086a082688ddf137437e5f7f20ffdd736a6f5b4e3ed1ecf1247", size = 5000434, upload-time = "2026-04-09T14:34:23.934Z" }, - { url = "https://files.pythonhosted.org/packages/73/37/b9f9b28b542d0e62bb9353753cbec7e321f7394fea10470c6b8e5739b61d/lxml-6.0.3-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:feb5b9ed7d0510663a78b94f2b417a41c41b42a7bb157ef398ef9d78e6f0fd50", size = 5201705, upload-time = "2026-04-09T14:34:26.328Z" }, - { url = "https://files.pythonhosted.org/packages/2d/26/9473de56eb74293c7061ff1a6ac352d5b89c83067f315accd73cf97a3b07/lxml-6.0.3-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:51014ee2ab2091dcd9cdef92532f0a1addb7c2cc52a2bd70682e441363de5c0d", size = 5329269, upload-time = "2026-04-09T14:34:29.563Z" }, - { url = "https://files.pythonhosted.org/packages/a2/a3/502f97b6221e0958da94fde5eb17119f2104694a88126ef82fa189d5d7a4/lxml-6.0.3-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:abc39c4fb67f029400608f9a3a4a3f351ccb3c061b05fd3ad113e4cfbba8a8ee", size = 4658312, upload-time = "2026-04-09T14:34:31.62Z" }, - { url = "https://files.pythonhosted.org/packages/d1/26/935d0297d1c272282e950986f14f044c8e4c34e60a8774bc993d26ddcf32/lxml-6.0.3-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:38652c280cf50cc5cf955e3d0b691fa6a97046d84407bbae340d8e353f9014ef", size = 5264811, upload-time = "2026-04-09T14:34:33.566Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a0/4755420775ded42b4cc9017357ce72ee7cd08fbfb72da3ac7e48fa2326bb/lxml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3de55b53f69ffa2fcfd897bd8a7e62f0f88a40a8a0c544e171e813f9d4ddbf5", size = 5043997, upload-time = "2026-04-09T14:34:35.506Z" }, - { url = "https://files.pythonhosted.org/packages/b7/54/61f21fcf0b5c0f30e58c369aacfa01f5a21ef0f8c9c773c413010c18a705/lxml-6.0.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd4f70e091f2df300396bc9ce36963f90b87611324c2ca750072a6e6375beba2", size = 4711595, upload-time = "2026-04-09T14:34:38.195Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9e/b9f73274a7e3819c821033ea9d8e777b297fcbe789765948d8c9d4fb9cfc/lxml-6.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:c157bfef4e3b19688eb4da783c5bfabf5a3ac1ac8d317e0906f3feb18d4c89b7", size = 5251294, upload-time = "2026-04-09T14:34:40.461Z" }, - { url = "https://files.pythonhosted.org/packages/38/8c/73e463041bad522c348c8a8c908a63c32f80215cff596210bbf24d69b3ee/lxml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8d10a75e4d0a6a9ac2fec2f7ade682f468b51935102c70dab638fa4e94ffcb04", size = 5224927, upload-time = "2026-04-09T14:34:42.986Z" }, - { url = "https://files.pythonhosted.org/packages/3f/05/42820ad63897bfd35cb7e591d79e8d21524c9da1520fa156b71d32f6953b/lxml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:d573b81c29e20b1513afa386a544797a99cecde5497e6c77b6dfa4484112c819", size = 3593261, upload-time = "2026-04-09T14:34:44.804Z" }, - { url = "https://files.pythonhosted.org/packages/e1/04/43c561e2293ede683f5259ceaccaf24ad6e830631123f197c1db483439ba/lxml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:ac63a1ef1899ccadace10ac937c41321672771378374c254e931d001448ae372", size = 4023698, upload-time = "2026-04-09T14:34:46.845Z" }, - { url = "https://files.pythonhosted.org/packages/8e/5f/13fde57b45a0f88b8c4bb02156fb115e99ad48354029cb522b543f502563/lxml-6.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:10bc4f37c28b4e1b3e901dde66e3a096eb128acf388d5b2962dc2941284293bb", size = 3666947, upload-time = "2026-04-09T14:34:48.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4c/552571c619edd607432cbbf25e312a5d02859f2a7de421494a644b48451e/lxml-6.0.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ad6952810349cbfb843fe15e8afc580b2712359ae42b1d2b05d097bd48c4aea4", size = 8570109, upload-time = "2026-04-09T14:34:50.969Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/cf08843a6a923cd1eef40797a31e61424ac257c43634b5c9cff3bee93696/lxml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b81ec1ecac3be8c1ff1e00ca1c1baf8122e87db9000cd2549963847bd5e3b41", size = 4623404, upload-time = "2026-04-09T14:34:53.79Z" }, - { url = "https://files.pythonhosted.org/packages/b6/59/ffde0037a781b10c854abdf9e34fbf60d8f375ce8026551982b9f26695cc/lxml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:448e69211e59c39f398990753d15ba49f7218ec128f64ac8012ef16762e509a3", size = 4929662, upload-time = "2026-04-09T14:34:55.763Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/c468055e45954a93e1bc043a964d327d6784552d6551dc2364a1f83c53a1/lxml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6289cb9145fbbc5b0e159c9fcd7fc09446dadc6b60b72c4d1012e80c7c727970", size = 5092106, upload-time = "2026-04-09T14:34:58.522Z" }, - { url = "https://files.pythonhosted.org/packages/59/a3/8400c79a6defe609e24ce7b580f48d53f08acbf4c998eede0083a89f16f0/lxml-6.0.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b68c29aac4788438b07d768057836de47589c7deaa3ad8dc4af488dfc27be388", size = 5004214, upload-time = "2026-04-09T14:35:00.531Z" }, - { url = "https://files.pythonhosted.org/packages/57/b5/797246619cd0831c8d239f91fd4683683abbe7144854c6f33c68a6ea9f42/lxml-6.0.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:50293e024afe5e2c25da2be68c8ceca8618912a0701a73f75b488317c8081aa6", size = 5630889, upload-time = "2026-04-09T14:35:02.89Z" }, - { url = "https://files.pythonhosted.org/packages/a0/fa/b86302385dc896d02ebb2803e4522a923acaa30e6cb35223492257ee24ab/lxml-6.0.3-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac65c08ba1bd90f662cb1d5c79f7ae4c53b1c100f0bb6ec5df1f40ac29028a7e", size = 5237728, upload-time = "2026-04-09T14:35:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/9b/7d/812c054b7d15f4dfb3a6fc877c2936023fcd8ac8b53807f996c8c60c4f57/lxml-6.0.3-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:16fbcf06ae534b2fa5bcdc19fcf6abd9df2e74fe8563147d1c5a687a130efed4", size = 5349527, upload-time = "2026-04-09T14:35:08.121Z" }, - { url = "https://files.pythonhosted.org/packages/b8/4a/33a572874924809928747cd156b172b04cd19c1ec1d10925fc77dfeb676d/lxml-6.0.3-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:3a0484bd1e84f82766befcbd71cccd7307dacfe08071e4dbc1d9a9b498d321e8", size = 4693177, upload-time = "2026-04-09T14:35:10.4Z" }, - { url = "https://files.pythonhosted.org/packages/36/d5/71842813ca0c43718f641e770195e278832f8c01870eaac857a3de34448a/lxml-6.0.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c137f8c8419c3de93e2998131d94628805f148e52b34da6d7533454e4d78bc2a", size = 5243928, upload-time = "2026-04-09T14:35:12.393Z" }, - { url = "https://files.pythonhosted.org/packages/da/a7/330845ae467c6086ef35977c335bb252fa11490082335f9ccfd0465bdfb7/lxml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:775266571f7027b1d77f5fce18a247b24f51a4404bdc1b90ec56be9b1e3801b9", size = 5046937, upload-time = "2026-04-09T14:35:15.209Z" }, - { url = "https://files.pythonhosted.org/packages/02/3d/b58b0aee0cf7e0b7eb5d24795a129c634c6d07f032d8b902bb0859319d13/lxml-6.0.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:aa18653b795d2c273b8676f7ad2ca916d846d15864e335f746658e4c28eb5168", size = 4776758, upload-time = "2026-04-09T14:35:17.758Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4c/f421b50f08c1b724a24c4a778db8888d0a2d948b4dd08b80f4f05a0804ff/lxml-6.0.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbffd22fc8e4d80454efa968b0c93440a00b8b8a817ce0c29d2c6cb5ad324362", size = 5644912, upload-time = "2026-04-09T14:35:20.438Z" }, - { url = "https://files.pythonhosted.org/packages/a7/99/eabfedb111ca1f26c8fe890413eabc7e2b0010f075fdf5bceb42737c3894/lxml-6.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7373ede7ccb89e6f6e39c1423b3a4d4ee48035d3b4619a6addced5c8b48d0ecc", size = 5233509, upload-time = "2026-04-09T14:35:23.137Z" }, - { url = "https://files.pythonhosted.org/packages/9f/17/050a105ca1154025b68c19901d45292cbdcee6f25bd056c178ad6b55e534/lxml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e759ff1b244725fef428c6b54f3dab4954c293b2d242a5f2e79db5cc3873de51", size = 5260150, upload-time = "2026-04-09T14:35:25.385Z" }, - { url = "https://files.pythonhosted.org/packages/61/a0/ed83517d12e9fe00101a21fe08a168fd69f57875d9416353e2a38c401df7/lxml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:f179bae37ad673f57756b59f26833b7922230bef471fdb29492428f152bae8c6", size = 3595160, upload-time = "2026-04-09T14:35:27.519Z" }, - { url = "https://files.pythonhosted.org/packages/55/d3/101726831f45951fe3ddd03cffbd2a4ac6261fc63ada399e6f7051d43af6/lxml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:8eeec925ad7f81886d413b3a1f8715551f75543519229a9b35e957771e1826d5", size = 3996108, upload-time = "2026-04-09T14:35:29.608Z" }, - { url = "https://files.pythonhosted.org/packages/49/9f/ab1c58ad55bfcd4b55bafd98f19ff24f34315441f13aa787d5220def0702/lxml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:f96bba9a26a064ce9e11099bad12fb08384b64d3acc0acf94bf386ca5cf4f95f", size = 3658906, upload-time = "2026-04-09T14:35:32.451Z" }, - { url = "https://files.pythonhosted.org/packages/86/a6/2cdc9c5a634b1b890927f968febc2474fa3eb6fed99db82ea3c008bbbda4/lxml-6.0.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:83c1d75e9d124ab82a4ddaf59135112f0dc49526b47355e5928ae6126a68e236", size = 8559579, upload-time = "2026-04-09T14:35:35.644Z" }, - { url = "https://files.pythonhosted.org/packages/97/3c/adfbcdab17f89f72e069c5df5661c81e0511e3cdb353550f778e9ffaa08e/lxml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b683665d0287308adafc90a5617a51a508d8af8c7040693693bb333b5f4474fe", size = 4617332, upload-time = "2026-04-09T14:35:38.901Z" }, - { url = "https://files.pythonhosted.org/packages/5e/d4/ee1a5c734a5ad79024fa85808f3efc18d5733813141e2bb2726a7d9d8bea/lxml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ed31e5852cd938704bc6c7a3822cbf84c7fa00ebfa914a1b4e2392d44f45bdfb", size = 4922821, upload-time = "2026-04-09T14:35:41.521Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1f/87efcc0b93ba4f95303ec8f80164f3c50db20a3a5612a285133f9ad6cb7e/lxml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8922a30704a4421d69a19e0499db5861da686c0bccc3a79cf3946e3155cf25f9", size = 5081226, upload-time = "2026-04-09T14:35:44.02Z" }, - { url = "https://files.pythonhosted.org/packages/65/8b/fd0fadd9ec8a6ac9d694014ccdb9504e28705abb2e08c9ca23c609020325/lxml-6.0.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a1adb0e220cb8691202ba9d97646a06292657a122df4b92733861d42f7cf4d2", size = 4992884, upload-time = "2026-04-09T14:35:46.769Z" }, - { url = "https://files.pythonhosted.org/packages/68/75/2fb0e534225214c6386496b7847195d7297b913cf563c5ccea394afc346b/lxml-6.0.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:821fd53699eb498990c915ba955a392d07246454c9405e6c1d0692362503013d", size = 5613383, upload-time = "2026-04-09T14:35:49.303Z" }, - { url = "https://files.pythonhosted.org/packages/54/3a/8f560f8fb2f5f092e18ac7a13a94b77e0e5213fe7c424d12e98393dcc7d8/lxml-6.0.3-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04b7cedf52e125f86d0d426635e7fbe8e353d4cc272a1757888e3c072424381d", size = 5228398, upload-time = "2026-04-09T14:35:51.611Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d5/6bf993c02a0173eb5883ace61958c55c245d3daf7753fb5f931a9691b440/lxml-6.0.3-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:9d98063e6ae0da5084ec46952bb0a5ccb5e2cad168e32b4d65d1ec84e4b4ebd4", size = 5342198, upload-time = "2026-04-09T14:35:54.311Z" }, - { url = "https://files.pythonhosted.org/packages/bb/18/637130349ca6aa33b6dc4796732835ede5017a811c5f55763a1c468f7971/lxml-6.0.3-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:ce01ab3449015358f766a1950b3d818eedf9d4cdec3fa87e4eecaad10c0784db", size = 4699178, upload-time = "2026-04-09T14:35:56.647Z" }, - { url = "https://files.pythonhosted.org/packages/bb/19/239daafcc1cfa42b8aa6384509a9fd2cb1aa281679c6e8395adf9ccbc189/lxml-6.0.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d38c25bad123d6ce30bb37931d90a4e8a167cd796eeae9cd16c2bfce52718f8e", size = 5231869, upload-time = "2026-04-09T14:36:00.41Z" }, - { url = "https://files.pythonhosted.org/packages/0a/74/db7fcadc651b988502bed00d48acfd8b997ecb5dd52ebcc05f39bf946d9e/lxml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9b8e0779780026979f217603385995202f364adc9807bd21210d81b9f562fc4e", size = 5043669, upload-time = "2026-04-09T14:36:02.463Z" }, - { url = "https://files.pythonhosted.org/packages/55/99/af795b579182fa04aa87fcb0bd112e22705d982f71eb53874a8d356b4091/lxml-6.0.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8c082ad2398664213a4bb5d133e2eb8bf239220b7d6688f8c8ffa9050057501f", size = 4769745, upload-time = "2026-04-09T14:36:04.716Z" }, - { url = "https://files.pythonhosted.org/packages/52/4d/10e652edc55d206188a1b738d1033aad3497886d34cb7f5fc753e67ecb49/lxml-6.0.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfc80c74233fe01157ab550fb12b9d07a2f1fa7c5900cefb484e3bf02e856fbc", size = 5635496, upload-time = "2026-04-09T14:36:06.815Z" }, - { url = "https://files.pythonhosted.org/packages/ab/68/95371835ec15bb46feee27b090bcabbe579f4ad04efbef08e2713bcfea16/lxml-6.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c45bdcdc2ca6cf26fddff3faa5de7a2ed7c7f6016b3de80125313a37f972378", size = 5223564, upload-time = "2026-04-09T14:36:09.057Z" }, - { url = "https://files.pythonhosted.org/packages/aa/a6/0a9e5b63e8959487551be5d5496bb758ed2424c77ed7b25a9b8aae3b60c6/lxml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99457524afd384c330dc51e527976653d543ccadfa815d9f2d92c5911626e536", size = 5250124, upload-time = "2026-04-09T14:36:11.337Z" }, - { url = "https://files.pythonhosted.org/packages/d9/80/de3d3a790edf6d026c829fe8ccf54845058f57f8bb788e420c3b227eecef/lxml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:c8e3b8a54e65393ce1d5c7d9753fe756f0d96089e7163b20ddec3e5bb56a963e", size = 3596004, upload-time = "2026-04-09T14:36:13.446Z" }, - { url = "https://files.pythonhosted.org/packages/9f/cf/43c9a5926060e39d99593921f37d7e88f129bc32ab6266b8460483abd613/lxml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:724b26a38cef98d6869d00a33cb66083bee967598e44f6a8e53f1dd283c851b0", size = 3994750, upload-time = "2026-04-09T14:36:15.686Z" }, - { url = "https://files.pythonhosted.org/packages/e5/d3/b224dbc282bfef52d2e05645e405b5ed89c6391144dc09864229fe9ce88c/lxml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:f27373113fda6621e4201f529908a24c8a190c2af355aed4711dadca44db4673", size = 3657620, upload-time = "2026-04-09T14:36:17.952Z" }, - { url = "https://files.pythonhosted.org/packages/d3/40/b637359bacf3813f1174d15b08516020ba5beb355e04377105d561e6e00a/lxml-6.0.3-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8c08926678852a233bf1ef645c4d683d56107f814482f8f41b21ef2c7659790e", size = 8575318, upload-time = "2026-04-09T14:36:20.608Z" }, - { url = "https://files.pythonhosted.org/packages/7f/91/d5286a45202ed91f1e428e68c6e1c11bcb2b42715c48424871fc73485b05/lxml-6.0.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2ce76d113a7c3bf42761ec1de7ca615b0cbf9d8ae478eb1d6c20111d9c9fc098", size = 4623084, upload-time = "2026-04-09T14:36:24.015Z" }, - { url = "https://files.pythonhosted.org/packages/8a/5f/7ea1af571ee13ed1e5fba007fd83cd0794723ca76a51eed0ef9513363b1f/lxml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83eca62141314d641ebe8089ffa532bbf572ea07dd6255b58c40130d06bb2509", size = 4948797, upload-time = "2026-04-09T14:36:26.662Z" }, - { url = "https://files.pythonhosted.org/packages/82/be/3a9b8d787d9877cbe17e02ef5af2523bd14ecc177ce308397c485c56fe18/lxml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d8781d812bb8efd47c35651639da38980383ff0d0c1f3269ade23e3a90799079", size = 5085983, upload-time = "2026-04-09T14:36:29.486Z" }, - { url = "https://files.pythonhosted.org/packages/c4/2b/645abaef837b11414c81513c31b308a001fb8cd370f665c3ebc854be5ba5/lxml-6.0.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:19b079e81aa3a31b523a224b0dd46da4f56e1b1e248eef9a599e5c885c788813", size = 5031039, upload-time = "2026-04-09T14:36:31.735Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4f/561f30b77e9edbb373e2b6b7203a7d6ab219c495abca219536c66f3a44b2/lxml-6.0.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6c055bafdcb53e7f9f75e22c009cd183dd410475e21c296d599531d7f03d1bf5", size = 5646718, upload-time = "2026-04-09T14:36:34.127Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ba/2a72e673d109b563c2ab77097f2f4ca64e2927d2f04836ba07aaabe1da0e/lxml-6.0.3-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f1594a183cee73f9a1dbfd35871c4e04b461f47eeb9bcf80f7d7856b1b136d", size = 5239360, upload-time = "2026-04-09T14:36:37.195Z" }, - { url = "https://files.pythonhosted.org/packages/52/98/4e5a4ef87d846af90cc9c1ee2f8af2af34c221e620aad317b3a535361b93/lxml-6.0.3-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:a6380c5035598e4665272ad3fc86c96ddb2a220d4059cce5ba4b660f78346ad9", size = 5351233, upload-time = "2026-04-09T14:36:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b8/cff0af5fe48ede6b1949dc2e14171470c0c68a15789037c1fed90602b89d/lxml-6.0.3-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:143ac903fb6c9be6da613390825c8e8bb8c8d71517d43882031f6b9bc89770ef", size = 4696677, upload-time = "2026-04-09T14:36:42.037Z" }, - { url = "https://files.pythonhosted.org/packages/0c/6e/0b2a918fb15c30b00ff112df16c548df011db37b58d764bd17f47db74905/lxml-6.0.3-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4fff7d77f440378cd841e340398edf5dbefee334816efbf521bb6e31651e54e", size = 5250503, upload-time = "2026-04-09T14:36:44.417Z" }, - { url = "https://files.pythonhosted.org/packages/57/1b/4697918f9d4c2e643e2c59cedb37c2f3a9f76fb1217d767f6dff476813d8/lxml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:631567ffc3ddb989ccdcd28f6b9fa5aab1ec7fc0e99fe65572b006a6aad347e2", size = 5084563, upload-time = "2026-04-09T14:36:46.762Z" }, - { url = "https://files.pythonhosted.org/packages/7b/8c/d7ec96246f0632773912c6556288d3b6bb6580f3a967441ca4636ddc3f73/lxml-6.0.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:38acf7171535ffa7fff1fcec8b82ebd4e55cd02e581efe776928108421accaa1", size = 4737407, upload-time = "2026-04-09T14:36:49.826Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0c/603e35bf77aeb28c972f39eece35e7c0f6579ff33a7bed095cc2f7f942d9/lxml-6.0.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:06b9f3ac459b4565bbaa97aa5512aa7f9a1188c662f0108364f288f6daf35773", size = 5670919, upload-time = "2026-04-09T14:36:52.231Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/6d3f188e6705cf0bfd8b5788055c7381bb3ffa786dfba9fa0b0ed5778506/lxml-6.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2773dbe2cedee81f2769bd5d24ceb4037706cf032e1703513dd0e9476cd9375f", size = 5237771, upload-time = "2026-04-09T14:36:55.286Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4c/01639533b90e9ff622909c113df2ab2dbdd1d78540eb153d13b66a9c96ba/lxml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:30c437d8bb9a9a9edff27e85b694342e47a26a6abc249abe00584a4824f9d80d", size = 5263862, upload-time = "2026-04-09T14:36:58.247Z" }, - { url = "https://files.pythonhosted.org/packages/06/0e/bd1157d7b09d1f5e1d580c124203cee656130a3f8908365760a593b21daf/lxml-6.0.3-cp314-cp314-win32.whl", hash = "sha256:1b60a3a1205f869bd47874787c792087174453b1a869db4837bf5b3ff92be017", size = 3656378, upload-time = "2026-04-09T14:37:47.74Z" }, - { url = "https://files.pythonhosted.org/packages/c5/cc/d50cbce8cd5687670868bea33bbeefa0866c5e5d02c5e11c4a04c79fc45e/lxml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:5b6913a68d98c58c673667c864500ba31bc9b0f462effac98914e9a92ebacd2e", size = 4062518, upload-time = "2026-04-09T14:37:49.911Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c7/ece11a1e51390502894838aa384e9f98af7bef4d6806a927197153a16972/lxml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:1b36a3c73f2a6d9c2bfae78089ca7aedae5c2ee5fd5214a15f00b2f89e558ba7", size = 3741064, upload-time = "2026-04-09T14:37:52.185Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ae/918d7f89635fb6456cd732c12246c0e504dd9c49e8006f3593c9ecdb90ff/lxml-6.0.3-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:239e9a6be3a79c03ec200d26f7bb17a4414704a208059e20050bf161e2d8848a", size = 8826590, upload-time = "2026-04-09T14:37:00.862Z" }, - { url = "https://files.pythonhosted.org/packages/07/cf/bda0ae583758704719976b9ea69c8b089fa5f92e49683e517386539b21cf/lxml-6.0.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:16e5cbaa1a6351f2abefa4072e9aac1f09103b47fe7ab4496d54e5995b065162", size = 4735028, upload-time = "2026-04-09T14:37:03.602Z" }, - { url = "https://files.pythonhosted.org/packages/2f/0e/3bfb18778c6f73c7ead2d49a256501fa3052888b899826f5d1df1fbdf83b/lxml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89f8746c206d8cf2c167221831645d6cc2b24464afd9c428a5eb3fd34c584eb1", size = 4969184, upload-time = "2026-04-09T14:37:05.914Z" }, - { url = "https://files.pythonhosted.org/packages/29/e6/796c77751a682d6d1bb9aa3fe43851b41a21b0377100e246a4a83a81d668/lxml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5d559a84b2fd583e5bcf8ec4af1ec895f98811684d5fbd6524ea31a04f92d4ad", size = 5103548, upload-time = "2026-04-09T14:37:08.605Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5e/a02aee214f657f29d4690d88161de8ffb8f1b5139e792bae313b9479e317/lxml-6.0.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7966fbce2d18fde579d5593933d36ad98cc7c8dc7f2b1916d127057ce0415062", size = 5027775, upload-time = "2026-04-09T14:37:11.283Z" }, - { url = "https://files.pythonhosted.org/packages/20/e5/65dd25f2c366879d696d1c720af9a96fa0969d2d135a27b6140222fc6f68/lxml-6.0.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1f258e6aa0e6eda2c1199f5582c062c96c7d4a28d96d0c4daa79e39b3f2a764", size = 5595348, upload-time = "2026-04-09T14:37:13.618Z" }, - { url = "https://files.pythonhosted.org/packages/f7/1f/2f0e80d7fd2ad9755d771af4ad46ea14bf871bc5a1d2d365a3f948940ddf/lxml-6.0.3-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:738aef404c862d2c3cd951364ee7175c9d50e8290f5726611c4208c0fba8d186", size = 5224217, upload-time = "2026-04-09T14:37:16.519Z" }, - { url = "https://files.pythonhosted.org/packages/3b/28/e1aaeee7d6a4c9f24a3e4535a4e19ce64b99eefbe7437d325b61623b1817/lxml-6.0.3-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:5c35e5c3ed300990a46a144d3514465713f812b35dacfa83e928c60db7c90af7", size = 5312245, upload-time = "2026-04-09T14:37:19.387Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ac/9633cb919124473e03c62862b0494bf0e1705f902fbd9627be4f648bddfb/lxml-6.0.3-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:4ff774b43712b0cf40d9888a5494ca39aefe990c946511cc947b9fddcf74a29b", size = 4637952, upload-time = "2026-04-09T14:37:21.648Z" }, - { url = "https://files.pythonhosted.org/packages/50/aa/135baeea457d41989bafa78e437fe3a370c793aab0d8fb3da73ccae10095/lxml-6.0.3-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d20af2784c763928d0d0879cbc5a3739e4d81eefa0d68962d3478bff4c13e644", size = 5232782, upload-time = "2026-04-09T14:37:24.6Z" }, - { url = "https://files.pythonhosted.org/packages/0e/77/d05183ac8440cbc4c6fa386edb7ba9718bee4f097e58485b1cd1f9479d56/lxml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fdb7786ebefaa0dad0d399dfeaf146b370a14591af2f3aea59e06f931a426678", size = 5083889, upload-time = "2026-04-09T14:37:27.432Z" }, - { url = "https://files.pythonhosted.org/packages/6d/58/e9fda8fb82775491ad0290c7b17252f944b6c3a6974cd820d65910690351/lxml-6.0.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c71a387ea133481e725079cff22de45593bf0b834824de22829365ab1d2386c9", size = 4758658, upload-time = "2026-04-09T14:37:29.81Z" }, - { url = "https://files.pythonhosted.org/packages/8b/32/4aae9f004f79f9d200efd8343809cfe46077f8e5bd58f08708c320a20fcd/lxml-6.0.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:841b89fc3d910d61c7c267db6bb7dc3a8b3dac240edb66220fcdf96fe70a0552", size = 5619494, upload-time = "2026-04-09T14:37:33.482Z" }, - { url = "https://files.pythonhosted.org/packages/f9/49/407fa9e3c91e7c6d0762eaeedd50d4695bcd26db817e933ca689eb1f3df4/lxml-6.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:ac2d6cdafa29672d6a604c641bf67ace3fd0735ec6885501a94943379219ddbf", size = 5228386, upload-time = "2026-04-09T14:37:36.058Z" }, - { url = "https://files.pythonhosted.org/packages/99/92/39982f818acbb1dd67dd5d20c2a06bcb9f1f3b9a8ff0021e367904f82417/lxml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:609bf136a7339aeca2bd4268c7cd190f33d13118975fe9964eda8e5138f42802", size = 5247973, upload-time = "2026-04-09T14:37:38.836Z" }, - { url = "https://files.pythonhosted.org/packages/66/68/fcdbb78c8cda81a86e17b31abf103b7e474e474a09fb291a99e7a9b43eb8/lxml-6.0.3-cp314-cp314t-win32.whl", hash = "sha256:bf98f5f87f6484302e7cce4e2ca5af43562902852063d916c3e2f1c115fdce60", size = 3896249, upload-time = "2026-04-09T14:37:41.068Z" }, - { url = "https://files.pythonhosted.org/packages/88/fb/6292681ac4a4223b700569ce98f71662cb07c5a3ade4f346f5f0d5c574cf/lxml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d3d65e511e4e656ec67b472110f7a72cbf8547ca15f76fe74cffa4e97412a064", size = 4391091, upload-time = "2026-04-09T14:37:43.357Z" }, - { url = "https://files.pythonhosted.org/packages/99/39/a0f486360a6f1b36fd2f5eb62d037652bef503d82b6f853aee6664cdfcac/lxml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:cbc7ce67f85b92db97c92219985432be84dc1ba9a028e68c6933e89551234df2", size = 3816374, upload-time = "2026-04-09T14:37:45.532Z" }, - { url = "https://files.pythonhosted.org/packages/a5/7c/3889981b55e83af1a710b2b54d40d5a9c7a2f7eab2e00cba6ba608fbdd22/lxml-6.0.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:093786037b934ef4747b0e8a0e1599fe7df7dd8246e7f07d43bba1c4c8bd7b84", size = 3929454, upload-time = "2026-04-09T14:38:54.873Z" }, - { url = "https://files.pythonhosted.org/packages/0b/29/a88dfb805c882b4fc81ef35d342629715a482037a0acd78ea8114e115d76/lxml-6.0.3-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6364aa77b13e04459df6a9d2b806465287e7540955527e75ebd5fda48532913d", size = 4209854, upload-time = "2026-04-09T14:38:57.541Z" }, - { url = "https://files.pythonhosted.org/packages/ca/01/44e71ace8c72bbb9aeb38551a4d314508133da88daf0dd9120a648af74ce/lxml-6.0.3-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:955550c78afb2be47755bd1b8153724292a5b539cf3f21665b310c145d08e6f8", size = 4317247, upload-time = "2026-04-09T14:38:59.977Z" }, - { url = "https://files.pythonhosted.org/packages/18/1a/ec02aafa56ff7675873e8fd4b6c7747aceaae037767434359e75d0b1075b/lxml-6.0.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a9a79144a8051bc5fbb223fac895b87eb67b361f27b00c2ed4a07ee34246b90", size = 4250372, upload-time = "2026-04-09T14:39:02.289Z" }, - { url = "https://files.pythonhosted.org/packages/35/13/94acd22f85e34e22eb984b4ac3db4c1b0c1e3daa0433dac5053fd26954d8/lxml-6.0.3-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8243937d4673b46da90b4f5ea2627fd26842225e62e885828fdb8133aa1f7b32", size = 4401010, upload-time = "2026-04-09T14:39:04.598Z" }, - { url = "https://files.pythonhosted.org/packages/28/7a/b3e8ed85413a4bd5c4850dfbd1eb18be7428127be0986f2a679d9d6098ad/lxml-6.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5892d2ef99449ebd8e30544af5bc61fd9c30e9e989093a10589766422f6c5e1a", size = 3507669, upload-time = "2026-04-09T14:39:06.873Z" }, +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/5d/3bccad330292946f97962df9d5f2d3ae129cce6e212732a781e856b91e07/lxml-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cec05be8c876f92a5aa07b01d60bbb4d11cfbdd654cad0561c0d7b5c043a61b9", size = 8526232, upload-time = "2026-04-18T04:27:40.389Z" }, + { url = "https://files.pythonhosted.org/packages/a7/51/adc8826570a112f83bb4ddb3a2ab510bbc2ccd62c1b9fe1f34fae2d90b57/lxml-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9c03e048b6ce8e77b09c734e931584894ecd58d08296804ca2d0b184c933ce50", size = 4595448, upload-time = "2026-04-18T04:27:44.208Z" }, + { url = "https://files.pythonhosted.org/packages/54/84/5a9ec07cbe1d2334a6465f863b949a520d2699a755738986dcd3b6b89e3f/lxml-6.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:942454ff253da14218f972b23dc72fa4edf6c943f37edd19cd697618b626fac5", size = 4923771, upload-time = "2026-04-18T04:32:17.402Z" }, + { url = "https://files.pythonhosted.org/packages/a7/23/851cfa33b6b38adb628e45ad51fb27105fa34b2b3ba9d1d4aa7a9428dfe0/lxml-6.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d036ee7b99d5148072ac7c9b847193decdfeac633db350363f7bce4fff108f0e", size = 5068101, upload-time = "2026-04-18T04:32:21.437Z" }, + { url = "https://files.pythonhosted.org/packages/b0/38/41bf99c2023c6b79916ba057d83e9db21d642f473cac210201222882d38b/lxml-6.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ae5d8d5427f3cc317e7950f2da7ad276df0cfa37b8de2f5658959e618ea8512", size = 5002573, upload-time = "2026-04-18T04:32:25.373Z" }, + { url = "https://files.pythonhosted.org/packages/c2/20/053aa10bdc39747e1e923ce2d45413075e84f70a136045bb09e5eaca41d3/lxml-6.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:363e47283bde87051b821826e71dde47f107e08614e1aa312ba0c5711e77738c", size = 5202816, upload-time = "2026-04-18T04:32:29.393Z" }, + { url = "https://files.pythonhosted.org/packages/9a/da/bc710fad8bf04b93baee752c192eaa2210cd3a84f969d0be7830fea55802/lxml-6.1.0-cp311-cp311-manylinux_2_28_i686.whl", hash = "sha256:f504d861d9f2a8f94020130adac88d66de93841707a23a86244263d1e54682f5", size = 5329999, upload-time = "2026-04-18T04:32:34.019Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/bf035dedbdf7fab49411aa52e4236f3445e98d38647d85419e6c0d2806b9/lxml-6.1.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:23a5dc68e08ed13331d61815c08f260f46b4a60fdd1640bbeb82cf89a9d90289", size = 4659643, upload-time = "2026-04-18T04:32:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/22be31f33727a5e4c7b01b0a874503026e50329b259d3587e0b923cf964b/lxml-6.1.0-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f15401d8d3dbf239e23c818afc10c7207f7b95f9a307e092122b6f86dd43209a", size = 5265963, upload-time = "2026-04-18T04:32:41.881Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2b/d44d0e5c79226017f4ab8c87a802ebe4f89f97e6585a8e4166dffcdd7b6e/lxml-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fcf3da95e93349e0647d48d4b36a12783105bcc74cb0c416952f9988410846a3", size = 5045444, upload-time = "2026-04-18T04:32:44.512Z" }, + { url = "https://files.pythonhosted.org/packages/d3/c3/3f034fec1594c331a6dbf9491238fdcc9d66f68cc529e109ec75b97197e1/lxml-6.1.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:0d082495c5fcf426e425a6e28daaba1fcb6d8f854a4ff01effb1f1f381203eb9", size = 4712703, upload-time = "2026-04-18T04:32:47.16Z" }, + { url = "https://files.pythonhosted.org/packages/12/16/0b83fccc158218aca75a7aa33e97441df737950734246b9fffa39301603d/lxml-6.1.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:e3c4f84b24a1fcba435157d111c4b755099c6ff00a3daee1ad281817de75ed11", size = 5252745, upload-time = "2026-04-18T04:32:50.427Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ee/12e6c1b39a77666c02eaa77f94a870aaf63c4ac3a497b2d52319448b01c6/lxml-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:976a6b39b1b13e8c354ad8d3f261f3a4ac6609518af91bdb5094760a08f132c4", size = 5226822, upload-time = "2026-04-18T04:32:53.437Z" }, + { url = "https://files.pythonhosted.org/packages/34/20/c7852904858b4723af01d2fc14b5d38ff57cb92f01934a127ebd9a9e51aa/lxml-6.1.0-cp311-cp311-win32.whl", hash = "sha256:857efde87d365706590847b916baff69c0bc9252dc5af030e378c9800c0b10e3", size = 3594026, upload-time = "2026-04-18T04:27:31.903Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/d60c732b56da5085175c07c74b2df4e6d181b0c9a61e1691474f06ef4b39/lxml-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:183bfb45a493081943be7ea2b5adfc2b611e1cf377cefa8b8a8be404f45ef9a7", size = 4025114, upload-time = "2026-04-18T04:27:34.077Z" }, + { url = "https://files.pythonhosted.org/packages/c2/df/c84dcc175fd690823436d15b41cb920cd5ba5e14cd8bfb00949d5903b320/lxml-6.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:19f4164243fc206d12ed3d866e80e74f5bc3627966520da1a5f97e42c32a3f39", size = 3667742, upload-time = "2026-04-18T04:27:38.45Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d4/9326838b59dc36dfae42eec9656b97520f9997eee1de47b8316aaeed169c/lxml-6.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2f17a16cd8751e8eb233a7e41aecdf8e511712e00088bf9be455f604cd0d28d", size = 8570663, upload-time = "2026-04-18T04:27:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/d8/a4/053745ce1f8303ccbb788b86c0db3a91b973675cefc42566a188637b7c40/lxml-6.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f0cea5b1d3e6e77d71bd2b9972eb2446221a69dc52bb0b9c3c6f6e5700592d93", size = 4624024, upload-time = "2026-04-18T04:27:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/90/97/a517944b20f8fd0932ad2109482bee4e29fe721416387a363306667941f6/lxml-6.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fc46da94826188ed45cb53bd8e3fc076ae22675aea2087843d4735627f867c6d", size = 4930895, upload-time = "2026-04-18T04:32:56.29Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/e08a970727d556caa040a44773c7b7e3ad0f0d73dedc863543e9a8b931f2/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9147d8e386ec3b82c3b15d88927f734f565b0aaadef7def562b853adca45784a", size = 5093820, upload-time = "2026-04-18T04:32:58.94Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/2a5c2aa2c32016a226ca25d3e1056a8102ea6e1fe308bf50213586635400/lxml-6.1.0-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5715e0e28736a070f3f34a7ccc09e2fdcba0e3060abbcf61a1a5718ff6d6b105", size = 5005790, upload-time = "2026-04-18T04:33:01.272Z" }, + { url = "https://files.pythonhosted.org/packages/e3/38/a0db9be8f38ad6043ab9429487c128dd1d30f07956ef43040402f8da49e8/lxml-6.1.0-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4937460dc5df0cdd2f06a86c285c28afda06aefa3af949f9477d3e8df430c485", size = 5630827, upload-time = "2026-04-18T04:33:04.036Z" }, + { url = "https://files.pythonhosted.org/packages/31/ba/3c13d3fc24b7cacf675f808a3a1baabf43a30d0cd24c98f94548e9aa58eb/lxml-6.1.0-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc783ee3147e60a25aa0445ea82b3e8aabb83b240f2b95d32cb75587ff781814", size = 5240445, upload-time = "2026-04-18T04:33:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/55/ba/eeef4ccba09b2212fe239f46c1692a98db1878e0872ae320756488878a94/lxml-6.1.0-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:40d9189f80075f2e1f88db21ef815a2b17b28adf8e50aaf5c789bfe737027f32", size = 5350121, upload-time = "2026-04-18T04:33:09.365Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/1da87c7b587c38d0cbe77a01aae3b9c1c49ed47d76918ef3db8fc151b1ca/lxml-6.1.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:05b9b8787e35bec69e68daf4952b2e6dfcfb0db7ecf1a06f8cdfbbac4eb71aad", size = 4694949, upload-time = "2026-04-18T04:33:11.628Z" }, + { url = "https://files.pythonhosted.org/packages/a1/88/7db0fe66d5aaf128443ee1623dec3db1576f3e4c17751ec0ef5866468590/lxml-6.1.0-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0f08beb0182e3e9a86fae124b3c47a7b41b7b69b225e1377db983802404e54", size = 5243901, upload-time = "2026-04-18T04:33:13.95Z" }, + { url = "https://files.pythonhosted.org/packages/00/a8/1346726af7d1f6fca1f11223ba34001462b0a3660416986d37641708d57c/lxml-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73becf6d8c81d4c76b1014dbd3584cb26d904492dcf73ca85dc8bff08dcd6d2d", size = 5048054, upload-time = "2026-04-18T04:33:16.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/85057012f035d1a0c87e02f8c723ca3c3e6e0728bcf4cb62080b21b1c1e3/lxml-6.1.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1ae225f66e5938f4fa29d37e009a3bb3b13032ac57eb4eb42afa44f6e4054e69", size = 4777324, upload-time = "2026-04-18T04:33:19.832Z" }, + { url = "https://files.pythonhosted.org/packages/75/6c/ad2f94a91073ef570f33718040e8e160d5fb93331cf1ab3ca1323f939e2d/lxml-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:690022c7fae793b0489aa68a658822cea83e0d5933781811cabbf5ea3bcfe73d", size = 5645702, upload-time = "2026-04-18T04:33:22.436Z" }, + { url = "https://files.pythonhosted.org/packages/3b/89/0bb6c0bd549c19004c60eea9dc554dd78fd647b72314ef25d460e0d208c6/lxml-6.1.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:63aeafc26aac0be8aff14af7871249e87ea1319be92090bfd632ec68e03b16a5", size = 5232901, upload-time = "2026-04-18T04:33:26.21Z" }, + { url = "https://files.pythonhosted.org/packages/a1/d9/d609a11fb567da9399f525193e2b49847b5a409cdebe737f06a8b7126bdc/lxml-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:264c605ab9c0e4aa1a679636f4582c4d3313700009fac3ec9c3412ed0d8f3e1d", size = 5261333, upload-time = "2026-04-18T04:33:28.984Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3a/ac3f99ec8ac93089e7dd556f279e0d14c24de0a74a507e143a2e4b496e7c/lxml-6.1.0-cp312-cp312-win32.whl", hash = "sha256:56971379bc5ee8037c5a0f09fa88f66cdb7d37c3e38af3e45cf539f41131ac1f", size = 3596289, upload-time = "2026-04-18T04:27:42.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a7/0a915557538593cb1bbeedcd40e13c7a261822c26fecbbdb71dad0c2f540/lxml-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:bba078de0031c219e5dd06cf3e6bf8fb8e6e64a77819b358f53bb132e3e03366", size = 3997059, upload-time = "2026-04-18T04:27:46.764Z" }, + { url = "https://files.pythonhosted.org/packages/92/96/a5dc078cf0126fbfbc35611d77ecd5da80054b5893e28fb213a5613b9e1d/lxml-6.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:c3592631e652afa34999a088f98ba7dfc7d6aff0d535c410bea77a71743f3819", size = 3659552, upload-time = "2026-04-18T04:27:51.133Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://files.pythonhosted.org/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://files.pythonhosted.org/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://files.pythonhosted.org/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://files.pythonhosted.org/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://files.pythonhosted.org/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://files.pythonhosted.org/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://files.pythonhosted.org/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, + { url = "https://files.pythonhosted.org/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, + { url = "https://files.pythonhosted.org/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, + { url = "https://files.pythonhosted.org/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, + { url = "https://files.pythonhosted.org/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, + { url = "https://files.pythonhosted.org/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, + { url = "https://files.pythonhosted.org/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, + { url = "https://files.pythonhosted.org/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, + { url = "https://files.pythonhosted.org/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, + { url = "https://files.pythonhosted.org/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, + { url = "https://files.pythonhosted.org/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, + { url = "https://files.pythonhosted.org/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, + { url = "https://files.pythonhosted.org/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, + { url = "https://files.pythonhosted.org/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, + { url = "https://files.pythonhosted.org/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, + { url = "https://files.pythonhosted.org/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, + { url = "https://files.pythonhosted.org/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, + { url = "https://files.pythonhosted.org/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, + { url = "https://files.pythonhosted.org/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, + { url = "https://files.pythonhosted.org/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, + { url = "https://files.pythonhosted.org/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, + { url = "https://files.pythonhosted.org/packages/f2/88/55143966481409b1740a3ac669e611055f49efd68087a5ce41582325db3e/lxml-6.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:546b66c0dd1bb8d9fa89d7123e5fa19a8aff3a1f2141eb22df96112afb17b842", size = 3930134, upload-time = "2026-04-18T04:32:35.008Z" }, + { url = "https://files.pythonhosted.org/packages/b5/97/28b985c2983938d3cb696dd5501423afb90a8c3e869ef5d3c62569282c0f/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5cfa1a34df366d9dc0d5eaf420f4cf2bb1e1bebe1066d1c2fc28c179f8a4004c", size = 4210749, upload-time = "2026-04-18T04:36:03.626Z" }, + { url = "https://files.pythonhosted.org/packages/29/67/dfab2b7d58214921935ccea7ce9b3df9b7d46f305d12f0f532ac7cf6b804/lxml-6.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db88156fcf544cdbf0d95588051515cfdfd4c876fc66444eb98bceb5d6db76de", size = 4318463, upload-time = "2026-04-18T04:36:06.309Z" }, + { url = "https://files.pythonhosted.org/packages/32/a2/4ac7eb32a4d997dd352c32c32399aae27b3f268d440e6f9cfa405b575d2f/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:07f98f5496f96bf724b1e3c933c107f0cbf2745db18c03d2e13a291c3afd2635", size = 4251124, upload-time = "2026-04-18T04:36:09.056Z" }, + { url = "https://files.pythonhosted.org/packages/33/ef/d6abd850bb4822f9b720cfe36b547a558e694881010ff7d012191e8769c6/lxml-6.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4642e04449a1e164b5ff71ffd901ddb772dfabf5c9adf1b7be5dffe1212bc037", size = 4401758, upload-time = "2026-04-18T04:36:11.803Z" }, + { url = "https://files.pythonhosted.org/packages/40/44/3ee09a5b60cb44c4f2fbc1c9015cfd6ff5afc08f991cab295d3024dcbf2d/lxml-6.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:7da13bb6fbadfafb474e0226a30570a3445cfd47c86296f2446dafbd77079ace", size = 3508860, upload-time = "2026-04-18T04:32:48.619Z" }, ] [[package]] @@ -1891,34 +1815,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, ] -[[package]] -name = "ms-active-directory" -version = "1.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dnspython" }, - { name = "ldap3" }, - { name = "pyasn1" }, - { name = "pycryptodome" }, - { name = "pytz" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d0/a0/da7aaa5c87d155f2af60db0db3ab12eec50bfdeeca6f6cd1559ca92375c0/ms_active_directory-1.14.1.tar.gz", hash = "sha256:86c3b9de8b8b5546104f0fe480db689b1a1d0a4109a4208603be4f981ce12040", size = 155782, upload-time = "2024-09-06T01:28:00.609Z" } - -[[package]] -name = "msal" -version = "1.36.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography", marker = "sys_platform == 'win32'" }, - { name = "pyjwt", extra = ["crypto"], marker = "sys_platform == 'win32'" }, - { name = "requests", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/de/cb/b02b0f748ac668922364ccb3c3bff5b71628a05f5adfec2ba2a5c3031483/msal-1.36.0.tar.gz", hash = "sha256:3f6a4af2b036b476a4215111c4297b4e6e236ed186cd804faefba23e4990978b", size = 174217, upload-time = "2026-04-09T10:20:33.525Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/d3/414d1f0a5f6f4fe5313c2b002c54e78a3332970feb3f5fed14237aa17064/msal-1.36.0-py3-none-any.whl", hash = "sha256:36ecac30e2ff4322d956029aabce3c82301c29f0acb1ad89b94edcabb0e58ec4", size = 121547, upload-time = "2026-04-09T10:20:32.336Z" }, -] - [[package]] name = "nbclient" version = "0.10.4" @@ -2119,11 +2015,11 @@ wheels = [ [[package]] name = "packaging" -version = "26.0" +version = "26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] [[package]] @@ -2314,11 +2210,11 @@ wheels = [ [[package]] name = "prometheus-client" -version = "0.24.1" +version = "0.25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/fb/d9aa83ffe43ce1f19e557c0971d04b90561b0cfd50762aafb01968285553/prometheus_client-0.25.0.tar.gz", hash = "sha256:5e373b75c31afb3c86f1a52fa1ad470c9aace18082d39ec0d2f918d11cc9ba28", size = 86035, upload-time = "2026-04-09T19:53:42.359Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9b/d4b1e644385499c8346fa9b622a3f030dce14cd6ef8a1871c221a17a67e7/prometheus_client-0.25.0-py3-none-any.whl", hash = "sha256:d5aec89e349a6ec230805d0df882f3807f74fd6c1a2fa86864e3c2279059fed1", size = 64154, upload-time = "2026-04-09T19:53:41.324Z" }, ] [[package]] @@ -2379,15 +2275,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] -[[package]] -name = "pyasn1" -version = "0.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" }, -] - [[package]] name = "pycparser" version = "3.0" @@ -2397,36 +2284,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] -[[package]] -name = "pycryptodome" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/a6/8452177684d5e906854776276ddd34eca30d1b1e15aa1ee9cefc289a33f5/pycryptodome-3.23.0.tar.gz", hash = "sha256:447700a657182d60338bab09fdb27518f8856aecd80ae4c6bdddb67ff5da44ef", size = 4921276, upload-time = "2025-05-17T17:21:45.242Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/5d/bdb09489b63cd34a976cc9e2a8d938114f7a53a74d3dd4f125ffa49dce82/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0011f7f00cdb74879142011f95133274741778abba114ceca229adbf8e62c3e4", size = 2495152, upload-time = "2025-05-17T17:20:20.833Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/7840250ed4cc0039c433cd41715536f926d6e86ce84e904068eb3244b6a6/pycryptodome-3.23.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:90460fc9e088ce095f9ee8356722d4f10f86e5be06e2354230a9880b9c549aae", size = 1639348, upload-time = "2025-05-17T17:20:23.171Z" }, - { url = "https://files.pythonhosted.org/packages/ee/f0/991da24c55c1f688d6a3b5a11940567353f74590734ee4a64294834ae472/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4764e64b269fc83b00f682c47443c2e6e85b18273712b98aa43bcb77f8570477", size = 2184033, upload-time = "2025-05-17T17:20:25.424Z" }, - { url = "https://files.pythonhosted.org/packages/54/16/0e11882deddf00f68b68dd4e8e442ddc30641f31afeb2bc25588124ac8de/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7", size = 2270142, upload-time = "2025-05-17T17:20:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/d5/fc/4347fea23a3f95ffb931f383ff28b3f7b1fe868739182cb76718c0da86a1/pycryptodome-3.23.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d97618c9c6684a97ef7637ba43bdf6663a2e2e77efe0f863cce97a76af396446", size = 2309384, upload-time = "2025-05-17T17:20:30.765Z" }, - { url = "https://files.pythonhosted.org/packages/6e/d9/c5261780b69ce66d8cfab25d2797bd6e82ba0241804694cd48be41add5eb/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a53a4fe5cb075075d515797d6ce2f56772ea7e6a1e5e4b96cf78a14bac3d265", size = 2183237, upload-time = "2025-05-17T17:20:33.736Z" }, - { url = "https://files.pythonhosted.org/packages/5a/6f/3af2ffedd5cfa08c631f89452c6648c4d779e7772dfc388c77c920ca6bbf/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:763d1d74f56f031788e5d307029caef067febf890cd1f8bf61183ae142f1a77b", size = 2343898, upload-time = "2025-05-17T17:20:36.086Z" }, - { url = "https://files.pythonhosted.org/packages/9a/dc/9060d807039ee5de6e2f260f72f3d70ac213993a804f5e67e0a73a56dd2f/pycryptodome-3.23.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:954af0e2bd7cea83ce72243b14e4fb518b18f0c1649b576d114973e2073b273d", size = 2269197, upload-time = "2025-05-17T17:20:38.414Z" }, - { url = "https://files.pythonhosted.org/packages/f9/34/e6c8ca177cb29dcc4967fef73f5de445912f93bd0343c9c33c8e5bf8cde8/pycryptodome-3.23.0-cp313-cp313t-win32.whl", hash = "sha256:257bb3572c63ad8ba40b89f6fc9d63a2a628e9f9708d31ee26560925ebe0210a", size = 1768600, upload-time = "2025-05-17T17:20:40.688Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1d/89756b8d7ff623ad0160f4539da571d1f594d21ee6d68be130a6eccb39a4/pycryptodome-3.23.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6501790c5b62a29fcb227bd6b62012181d886a767ce9ed03b303d1f22eb5c625", size = 1799740, upload-time = "2025-05-17T17:20:42.413Z" }, - { url = "https://files.pythonhosted.org/packages/5d/61/35a64f0feaea9fd07f0d91209e7be91726eb48c0f1bfc6720647194071e4/pycryptodome-3.23.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9a77627a330ab23ca43b48b130e202582e91cc69619947840ea4d2d1be21eb39", size = 1703685, upload-time = "2025-05-17T17:20:44.388Z" }, - { url = "https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:187058ab80b3281b1de11c2e6842a357a1f71b42cb1e15bce373f3d238135c27", size = 2495627, upload-time = "2025-05-17T17:20:47.139Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4e/a066527e079fc5002390c8acdd3aca431e6ea0a50ffd7201551175b47323/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cfb5cd445280c5b0a4e6187a7ce8de5a07b5f3f897f235caa11f1f435f182843", size = 1640362, upload-time = "2025-05-17T17:20:50.392Z" }, - { url = "https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67bd81fcbe34f43ad9422ee8fd4843c8e7198dd88dd3d40e6de42ee65fbe1490", size = 2182625, upload-time = "2025-05-17T17:20:52.866Z" }, - { url = "https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8987bd3307a39bc03df5c8e0e3d8be0c4c3518b7f044b0f4c15d1aa78f52575", size = 2268954, upload-time = "2025-05-17T17:20:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/f9/c5/ffe6474e0c551d54cab931918127c46d70cab8f114e0c2b5a3c071c2f484/pycryptodome-3.23.0-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0698f65e5b570426fc31b8162ed4603b0c2841cbb9088e2b01641e3065915b", size = 2308534, upload-time = "2025-05-17T17:20:57.279Z" }, - { url = "https://files.pythonhosted.org/packages/18/28/e199677fc15ecf43010f2463fde4c1a53015d1fe95fb03bca2890836603a/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:53ecbafc2b55353edcebd64bf5da94a2a2cdf5090a6915bcca6eca6cc452585a", size = 2181853, upload-time = "2025-05-17T17:20:59.322Z" }, - { url = "https://files.pythonhosted.org/packages/ce/ea/4fdb09f2165ce1365c9eaefef36625583371ee514db58dc9b65d3a255c4c/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_i686.whl", hash = "sha256:156df9667ad9f2ad26255926524e1c136d6664b741547deb0a86a9acf5ea631f", size = 2342465, upload-time = "2025-05-17T17:21:03.83Z" }, - { url = "https://files.pythonhosted.org/packages/22/82/6edc3fc42fe9284aead511394bac167693fb2b0e0395b28b8bedaa07ef04/pycryptodome-3.23.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:dea827b4d55ee390dc89b2afe5927d4308a8b538ae91d9c6f7a5090f397af1aa", size = 2267414, upload-time = "2025-05-17T17:21:06.72Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886", size = 1768484, upload-time = "2025-05-17T17:21:08.535Z" }, - { url = "https://files.pythonhosted.org/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2", size = 1799636, upload-time = "2025-05-17T17:21:10.393Z" }, - { url = "https://files.pythonhosted.org/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c", size = 1703675, upload-time = "2025-05-17T17:21:13.146Z" }, -] - [[package]] name = "pycryptodomex" version = "3.23.0" @@ -2539,29 +2396,29 @@ wheels = [ [[package]] name = "pydantic-extra-types" -version = "2.11.2" +version = "2.11.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/d3/3be31542180c0300b6860129ff1e3a428f3ef580727616ce22462626129b/pydantic_extra_types-2.11.2.tar.gz", hash = "sha256:3a2b83b61fe920925688e7838b59caa90a45637d1dbba2b1364b8d1f7ff72a0a", size = 203929, upload-time = "2026-04-05T20:50:51.556Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/71/dba38ee2651f84f7842206adbd2233d8bbdb59fb85e9fa14232486a8c471/pydantic_extra_types-2.11.1.tar.gz", hash = "sha256:46792d2307383859e923d8fcefa82108b1a141f8a9c0198982b3832ab5ef1049", size = 172002, upload-time = "2026-03-16T08:08:03.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/a4/7b6ab05c18d6c6e682382a0f0235301684452c4131a869f45961d1d032c9/pydantic_extra_types-2.11.2-py3-none-any.whl", hash = "sha256:683b8943252543e49760f89733b1519bc62f31d1a287ebbdc5a7b7959fb4acfd", size = 82851, upload-time = "2026-04-05T20:50:50.036Z" }, + { url = "https://files.pythonhosted.org/packages/17/c1/3226e6d7f5a4f736f38ac11a6fbb262d701889802595cdb0f53a885ac2e0/pydantic_extra_types-2.11.1-py3-none-any.whl", hash = "sha256:1722ea2bddae5628ace25f2aa685b69978ef533123e5638cfbddb999e0100ec1", size = 79526, upload-time = "2026-03-16T08:08:02.533Z" }, ] [[package]] name = "pydantic-settings" -version = "2.13.1" +version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, ] [[package]] @@ -2593,20 +2450,6 @@ version = "1.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/66/ca/823d5c74a73d6b8b08e1f5aea12468ef334f0732c65cbb18df2a7f285c87/pygraphviz-1.14.tar.gz", hash = "sha256:c10df02377f4e39b00ae17c862f4ee7e5767317f1c6b2dfd04cea6acc7fc2bea", size = 106003, upload-time = "2024-09-29T18:31:12.471Z" } -[[package]] -name = "pyjwt" -version = "2.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, -] - -[package.optional-dependencies] -crypto = [ - { name = "cryptography", marker = "sys_platform == 'win32'" }, -] - [[package]] name = "pykeepass" version = "4.1.1.post1" @@ -2671,15 +2514,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl", hash = "sha256:132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2", size = 15021, upload-time = "2026-03-29T04:39:55.266Z" }, ] -[[package]] -name = "pytz" -version = "2026.1.post1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, -] - [[package]] name = "pywinpty" version = "3.0.3" @@ -2889,15 +2723,15 @@ wheels = [ [[package]] name = "rich" -version = "14.3.3" +version = "15.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] [[package]] @@ -3070,27 +2904,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, - { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, - { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, - { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, - { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, - { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, - { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, - { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, - { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, - { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, - { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, - { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, - { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, - { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, - { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, +version = "0.15.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264, upload-time = "2026-04-16T18:46:26.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943, upload-time = "2026-04-16T18:46:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592, upload-time = "2026-04-16T18:46:00.742Z" }, + { url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501, upload-time = "2026-04-16T18:46:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693, upload-time = "2026-04-16T18:46:41.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177, upload-time = "2026-04-16T18:46:21.717Z" }, + { url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886, upload-time = "2026-04-16T18:46:15.086Z" }, + { url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183, upload-time = "2026-04-16T18:46:07.944Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575, upload-time = "2026-04-16T18:46:31.687Z" }, + { url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537, upload-time = "2026-04-16T18:46:36.988Z" }, + { url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813, upload-time = "2026-04-16T18:46:24.182Z" }, + { url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136, upload-time = "2026-04-16T18:46:39.802Z" }, + { url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701, upload-time = "2026-04-16T18:46:10.381Z" }, + { url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887, upload-time = "2026-04-16T18:46:29.157Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316, upload-time = "2026-04-16T18:46:19.462Z" }, + { url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535, upload-time = "2026-04-16T18:46:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692, upload-time = "2026-04-16T18:46:17.268Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614, upload-time = "2026-04-16T18:46:34.487Z" }, ] [[package]] @@ -3587,21 +3421,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b wheels = [ { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, ] - -[[package]] -name = "winkerberos" -version = "0.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/6c/455f043bc28694a278125d1fc2ab7cbf0ce0953c97bbe1021f08fd19c7b8/winkerberos-0.13.0.tar.gz", hash = "sha256:f3fbb67346fe8ed697e125724b0699d5c2a15b9a5f9151d25a1be88df8dac427", size = 35677, upload-time = "2025-12-03T14:17:29.882Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/09/05c4d2fb93f5478fd1b6146c4fa3fbb80839576a34062e5677f2dec3a430/winkerberos-0.13.0-cp311-cp311-win32.whl", hash = "sha256:a23c83854650416545000c4630e94b16fa14c7b400bd5f08a79718e04eff9135", size = 25642, upload-time = "2025-12-03T14:17:17.42Z" }, - { url = "https://files.pythonhosted.org/packages/6e/5b/bafa1cfb9f047be139ffae330f6eafa0487f8bf82164ead756e0bc2bc047/winkerberos-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bc03e66a737bfd11964e6cdc5f03a8cd0baed798f991b1467075c65980c4157", size = 27931, upload-time = "2025-12-03T14:17:18.708Z" }, - { url = "https://files.pythonhosted.org/packages/3a/fa/02de79d7dbec9122a6778678ed432ebffb228c48b16cfba3007c45a6e8fd/winkerberos-0.13.0-cp312-cp312-win32.whl", hash = "sha256:3454b8bb9c11091e4775a8bd692dfbe45f2eab12f3a4837b820c2505088dfdd2", size = 25675, upload-time = "2025-12-03T14:17:20.052Z" }, - { url = "https://files.pythonhosted.org/packages/52/c2/ff9074cf423d82bdfb48ac89e64f360533ba4e2079e8485be8377a8c54fe/winkerberos-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:59f01879c62adcda5af857fd78d2b2dfdfd99cf6179b92d38e2f2bd12db75bf7", size = 27946, upload-time = "2025-12-03T14:17:21.22Z" }, - { url = "https://files.pythonhosted.org/packages/92/83/b1f52594cc2c3ce18c67a04aecb0cb4fb3f4769c268d194cc5f4863150fa/winkerberos-0.13.0-cp313-cp313-win32.whl", hash = "sha256:38fefdfc77a7f82c3cc9f83c7d1b6f242e6d3ea200bfde9b640f7dfe9fdf9bda", size = 25671, upload-time = "2025-12-03T14:17:23.235Z" }, - { url = "https://files.pythonhosted.org/packages/9c/26/b17649b0707e4d8cd9d0d4ceadcef06eff2fc76fcb444cb187763158ae63/winkerberos-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:c45e84a35a3b87b88d0e6d7b55d40712dc021f80af3cb9e81091651e6a73510d", size = 27941, upload-time = "2025-12-03T14:17:24.627Z" }, - { url = "https://files.pythonhosted.org/packages/80/d9/d12d310fdf9ace70f7469ecfd9f112dc39cb7e1f77348228c06a6bd72c57/winkerberos-0.13.0-cp314-cp314-win32.whl", hash = "sha256:46cc29fa95744076a0dd2a167158574826509a5e4aa052b81a2b535aab4af14a", size = 26185, upload-time = "2025-12-03T14:17:25.63Z" }, - { url = "https://files.pythonhosted.org/packages/97/7c/5a418e8d292e3fea1012ccf029b38fae430542fab1beaf6fc60cf138cc08/winkerberos-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:5d5add54d10e31671f7c28c90ccafe98b45cec6d7519949ba30add51e34aee9a", size = 28476, upload-time = "2025-12-03T14:17:26.629Z" }, - { url = "https://files.pythonhosted.org/packages/29/ba/cd8186479046b7a749cee8d4d9fd50e3ce3330d8ea611efe4b8b741f0c3b/winkerberos-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5bc5e40a816d94d4a5abd665fe62088c1ee91ee9a1f5d787032a63004842fedf", size = 26500, upload-time = "2025-12-03T14:17:27.873Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a6/cc5f24b3f1a46a826b7e30ef56fdc1fe22315fef96de8e22afbdd5d98e7a/winkerberos-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:441884c0bda4bee0125fdbd7fee6a232dab58b4a64be8950eb17a8a7404a5440", size = 28715, upload-time = "2025-12-03T14:17:28.813Z" }, -] From bd71db11534bb45a59828edb1f1db8fd5b8cd18f Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Mon, 20 Apr 2026 16:13:25 -0700 Subject: [PATCH 02/10] Fix namespace issues --- src/Extensions.csproj | 1 + src/Extensions/CalibrationLogic.bonsai | 150 ++++++++++++------------- src/Extensions/CreateHubOdorMixture.cs | 105 +++++++++++++++++ 3 files changed, 180 insertions(+), 76 deletions(-) create mode 100644 src/Extensions/CreateHubOdorMixture.cs diff --git a/src/Extensions.csproj b/src/Extensions.csproj index 19ffa04..095da66 100644 --- a/src/Extensions.csproj +++ b/src/Extensions.csproj @@ -11,6 +11,7 @@ + \ No newline at end of file diff --git a/src/Extensions/CalibrationLogic.bonsai b/src/Extensions/CalibrationLogic.bonsai index a3741d4..c09ba15 100644 --- a/src/Extensions/CalibrationLogic.bonsai +++ b/src/Extensions/CalibrationLogic.bonsai @@ -1,11 +1,11 @@  - @@ -63,8 +63,8 @@ - - 0 + + Channel0 @@ -76,8 +76,8 @@ ThisOdorChannel - - OdorChannel + + OdorChannel @@ -89,8 +89,8 @@ - - IsEndValveCalibration + + IsEndValveCalibration @@ -134,8 +134,8 @@ OdorChannel - - 0 + + Channel0 @@ -151,8 +151,8 @@ OdorChannel - - 1 + + Channel1 @@ -168,8 +168,8 @@ OdorChannel - - 2 + + Channel2 @@ -182,15 +182,15 @@ - - true - 1 - 0 - 0 - NaN - true - 100 - 1000 + + true + 1 + 0 + 0 + NaN + true + 100 + 1000 @@ -256,8 +256,8 @@ - - 0 + + Channel0 @@ -271,7 +271,7 @@ Source1 - + Carrier @@ -308,7 +308,7 @@ TimeOff - + @@ -318,7 +318,6 @@ PT5S - PT0S @@ -336,7 +335,7 @@ TimeOn - + @@ -477,8 +476,8 @@ - - 3 + + Channel0 @@ -490,8 +489,8 @@ ThisOdorChannel - - OdorChannel + + OdorChannel @@ -503,8 +502,8 @@ - - IsEndValveCalibration + + IsEndValveCalibration @@ -549,15 +548,15 @@ - - true - 0 - 0 - 0 - NaN - true - 100 - 1000 + + true + 0 + 0 + 0 + NaN + true + 100 + 1000 @@ -621,8 +620,8 @@ - - 3 + + Channel0 @@ -636,7 +635,7 @@ Source1 - + Carrier @@ -673,7 +672,7 @@ TimeOff - + @@ -683,7 +682,6 @@ PT5S - PT0S @@ -724,8 +722,8 @@ OdorChannel - - 0 + + Channel0 @@ -741,8 +739,8 @@ OdorChannel - - 1 + + Channel1 @@ -758,8 +756,8 @@ OdorChannel - - 2 + + Channel2 @@ -772,15 +770,15 @@ - - true - 1 - 0 - 0 - NaN - true - 100 - 1000 + + true + 1 + 0 + 0 + NaN + true + 100 + 1000 @@ -838,7 +836,7 @@ TimeOn - + @@ -885,15 +883,15 @@ - - true - 0 - 0 - 0 - NaN - true - 100 - 1000 + + true + 0 + 0 + 0 + NaN + true + 100 + 1000 diff --git a/src/Extensions/CreateHubOdorMixture.cs b/src/Extensions/CreateHubOdorMixture.cs new file mode 100644 index 0000000..4e61a97 --- /dev/null +++ b/src/Extensions/CreateHubOdorMixture.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; +using Bonsai.Harp; +using Harp.Olfactometer; + + +[Description("Returns a list of pairs of Harp messages necessary to configure all olfactometers in a hub to deliver a given odor mixture.")] +public class CreateHubOdorMixture : Transform, IList> +{ + private int perOdorFlow = 100; + [Range(0, 900)] + [Editor(DesignTypes.SliderEditor, DesignTypes.UITypeEditor)] + [Description("The total desired flow of the odor mixture. This value will be used to automatically calculate the carrier(s) flow based on the total flow.")] + public int PerOdorFlow + { + get { return perOdorFlow; } + set { perOdorFlow = value; } + } + + + private int totalFlow = 1000; + [Range(100, 1000)] + [Editor(DesignTypes.SliderEditor, DesignTypes.UITypeEditor)] + [Description("The total desired flow at the end of the manifold. This value will be used to automatically calculate the carrier(s) flow.")] + public int TotalFlow + { + get { return totalFlow; } + set { totalFlow = value; } + } + + private int olfactometerChannelCount = 3; + public int OlfactometerChannelCount + { + get { return olfactometerChannelCount; } + set { olfactometerChannelCount = value; } + } + + private const int _MINIMUM_CARRIER_FLOW = 100; + + private IList ConstructMessage(IList channelConcentrations) + { + int nChannels = OlfactometerChannelCount; + if (channelConcentrations.Count > nChannels) + { + throw new ArgumentException("The number of channel concentrations provided " + channelConcentrations.Count + " does not match the expected number based on the olfactometer count " + nChannels + "."); + } + // We make sure all odors sum to 1 and then calculate the "real" flow for each channel based on the target odor flow + var adjustedFlow = channelConcentrations.Select(c => (int)(PerOdorFlow * c)) + .Concat(Enumerable.Repeat(0, nChannels - channelConcentrations.Count)); // We pad with zeros if there are fewer concentrations than channels + var carrierFlow = totalFlow - adjustedFlow.Sum(); + if (carrierFlow < _MINIMUM_CARRIER_FLOW) + { + throw new InvalidOperationException("The total odor flow exceeds the total flow minus the minimum carrier flow. Reduce the total target odor flow or the concentrations."); + } + var indexedConcentrations = Enumerable.Range(0, nChannels).Select(i => i < 3 ? 0 : ((i - 3) / 4) + 1).Zip(adjustedFlow, (index, flow) => Tuple.Create(index, flow)).ToList(); + + List messages = new List(); + foreach (var group in indexedConcentrations.GroupBy(x => x.Item1)) // Group by olfactometer index + { + var concentrationList = group.Select(x => x.Item2); + var olfactometerIndex = group.Key; + var channel3Flow = olfactometerIndex > 0 ? concentrationList.ElementAtOrDefault(3) : totalFlow; // for the first olfactometer we use it as a carrier for the blank, + var flows = Enumerable.Range(0, 4).Select(i => i < 3 ? concentrationList.ElementAtOrDefault(i) : channel3Flow).ToList(); + + var channelsTargetFlow = ChannelsTargetFlow.FromPayload(MessageType.Write, new ChannelsTargetFlowPayload( + flows[0], + flows[1], + flows[2], + flows[3], + olfactometerIndex == 0 ? carrierFlow : 0 // We explicitly turn off the air supply to the extension olfactometers since it should never be used + )); + OdorValves valves = OdorValves.None; + for (int i = 0; i < flows.Count; i++) + { + if (flows[i] > 0) + valves |= (OdorValves)(1 << i); + } + var odorValveState = OdorValveState.FromPayload(MessageType.Write, valves); + messages.Add(new OdorMixMessages() + { + ChannelsTargetFlow = channelsTargetFlow, + OdorValveState = odorValveState, + OlfactometerIndex = olfactometerIndex + }); + } + return messages; + } + + public override IObservable> Process(IObservable> source) + { + return source.Select(value => ConstructMessage(value)); + } +} + +public class OdorMixMessages +{ + public HarpMessage ChannelsTargetFlow { get; set; } + public HarpMessage OdorValveState { get; set; } + + public int OlfactometerIndex { get; set; } +} From 629f6eeaa5f8780357b6779e1a5db8011e88f767 Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:49:22 -0700 Subject: [PATCH 03/10] Refactor logic to support multiple olfactometers --- schema/aind_behavior_device_olfactometer.json | 29 +- src/Extensions.csproj | 2 +- ...indBehaviorDeviceOlfactometer.Generated.cs | 95 +- src/Extensions/CalibrationLogic.bonsai | 688 ++++------- .../CreateOneHotConcentrationArray.cs | 42 + src/Extensions/Hardware.bonsai | 89 +- src/Extensions/HardwareValidation.bonsai | 28 +- .../HarpOlfactometerExtensionManager.bonsai | 294 +++++ src/Extensions/Logging.bonsai | 322 +++-- src/Extensions/OdorControl.bonsai | 527 +++++++++ src/Extensions/UserInterface.bonsai | 1031 ++++++++++------- .../task_logic.py | 13 +- src/main.bonsai | 241 +--- 13 files changed, 2090 insertions(+), 1311 deletions(-) create mode 100644 src/Extensions/CreateOneHotConcentrationArray.cs create mode 100644 src/Extensions/HarpOlfactometerExtensionManager.bonsai create mode 100644 src/Extensions/OdorControl.bonsai diff --git a/schema/aind_behavior_device_olfactometer.json b/schema/aind_behavior_device_olfactometer.json index 5acdb32..cdc7f9e 100644 --- a/schema/aind_behavior_device_olfactometer.json +++ b/schema/aind_behavior_device_olfactometer.json @@ -331,6 +331,24 @@ "title": "BaseModel", "type": "object" }, + "ChannelToCalibrate": { + "properties": { + "odor_index": { + "title": "Olfactometer odor channel index. This is an absolute count across all olfactometer channels", + "type": "integer" + }, + "odor_configuration": { + "$ref": "#/$defs/OlfactometerChannelConfig", + "title": "Olfactometer channel configuration" + } + }, + "required": [ + "odor_index", + "odor_configuration" + ], + "title": "ChannelToCalibrate", + "type": "object" + }, "ConnectedClockOutput": { "properties": { "target_device": { @@ -681,15 +699,12 @@ "type": "string" }, "channel_config": { - "additionalProperties": { - "$ref": "#/$defs/OlfactometerChannelConfig" - }, - "description": "Configuration of olfactometer channels", - "propertyNames": { - "$ref": "#/$defs/OlfactometerChannel" + "description": "List of olfactometer channels to calibrate with their configurations", + "items": { + "$ref": "#/$defs/ChannelToCalibrate" }, "title": "Channel Config", - "type": "object" + "type": "array" }, "full_flow_rate": { "default": 1000, diff --git a/src/Extensions.csproj b/src/Extensions.csproj index 095da66..f73c758 100644 --- a/src/Extensions.csproj +++ b/src/Extensions.csproj @@ -5,7 +5,7 @@ - + diff --git a/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs b/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs index 9c73596..642cc10 100644 --- a/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs +++ b/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs @@ -202,6 +202,86 @@ public override string ToString() } + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] + [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] + [Bonsai.CombinatorAttribute(MethodName="Generate")] + public partial class ChannelToCalibrate + { + + private int _odorIndex; + + private OlfactometerChannelConfig _odorConfiguration; + + public ChannelToCalibrate() + { + _odorConfiguration = new OlfactometerChannelConfig(); + } + + protected ChannelToCalibrate(ChannelToCalibrate other) + { + _odorIndex = other._odorIndex; + _odorConfiguration = other._odorConfiguration; + } + + [Newtonsoft.Json.JsonPropertyAttribute("odor_index", Required=Newtonsoft.Json.Required.Always)] + public int OdorIndex + { + get + { + return _odorIndex; + } + set + { + _odorIndex = value; + } + } + + [System.Xml.Serialization.XmlIgnoreAttribute()] + [Newtonsoft.Json.JsonPropertyAttribute("odor_configuration", Required=Newtonsoft.Json.Required.Always)] + public OlfactometerChannelConfig OdorConfiguration + { + get + { + return _odorConfiguration; + } + set + { + _odorConfiguration = value; + } + } + + public System.IObservable Generate() + { + return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new ChannelToCalibrate(this))); + } + + public System.IObservable Generate(System.IObservable source) + { + return System.Reactive.Linq.Observable.Select(source, _ => new ChannelToCalibrate(this)); + } + + protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) + { + stringBuilder.Append("OdorIndex = " + _odorIndex + ", "); + stringBuilder.Append("OdorConfiguration = " + _odorConfiguration); + return true; + } + + public override string ToString() + { + System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); + stringBuilder.Append(GetType().Name); + stringBuilder.Append(" { "); + if (PrintMembers(stringBuilder)) + { + stringBuilder.Append(" "); + } + stringBuilder.Append("}"); + return stringBuilder.ToString(); + } + } + + [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] [Bonsai.CombinatorAttribute(MethodName="Generate")] @@ -994,7 +1074,7 @@ public partial class OlfactometerCalibrationParameters private string _aindBehaviorServicesPkgVersion; - private System.Collections.Generic.Dictionary _channelConfig; + private System.Collections.Generic.List _channelConfig; private double _fullFlowRate; @@ -1007,6 +1087,7 @@ public partial class OlfactometerCalibrationParameters public OlfactometerCalibrationParameters() { _aindBehaviorServicesPkgVersion = "0.13.7"; + _channelConfig = new System.Collections.Generic.List(); _fullFlowRate = 1000D; _nRepeatsPerStimulus = 1; _timeOn = 1D; @@ -1055,12 +1136,12 @@ public string AindBehaviorServicesPkgVersion } /// - /// Configuration of olfactometer channels + /// List of olfactometer channels to calibrate with their configurations /// [System.Xml.Serialization.XmlIgnoreAttribute()] [Newtonsoft.Json.JsonPropertyAttribute("channel_config")] - [System.ComponentModel.DescriptionAttribute("Configuration of olfactometer channels")] - public System.Collections.Generic.Dictionary ChannelConfig + [System.ComponentModel.DescriptionAttribute("List of olfactometer channels to calibrate with their configurations")] + public System.Collections.Generic.List ChannelConfig { get { @@ -1968,6 +2049,11 @@ public System.IObservable Process(System.IObservable source) return Process(source); } + public System.IObservable Process(System.IObservable source) + { + return Process(source); + } + public System.IObservable Process(System.IObservable source) { return Process(source); @@ -2029,6 +2115,7 @@ public System.IObservable Process(System.IObservable source) [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Transform)] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] + [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] diff --git a/src/Extensions/CalibrationLogic.bonsai b/src/Extensions/CalibrationLogic.bonsai index c09ba15..6c49849 100644 --- a/src/Extensions/CalibrationLogic.bonsai +++ b/src/Extensions/CalibrationLogic.bonsai @@ -2,10 +2,8 @@ @@ -29,16 +27,19 @@ Source1 - - Key - 1 - OdorChannel + thisOdor + + + thisOdor + + + CurrentOdorBeingCalibrated @@ -49,35 +50,11 @@ OdorEndValveState - TaskLogicParameters - - - ChannelConfig - - - OdorChannel - - - - - - - - - Channel0 - + thisOdor - - 1 - - - - ThisOdorChannel - - - - OdorChannel + + OdorChannel @@ -89,8 +66,8 @@ - - IsEndValveCalibration + + IsEndValveCalibration @@ -101,24 +78,25 @@ - Channel3AsCarrier + thisOdor - - - - + + OdorIndex - ThisOdorChannel - - - FlowRate + olfactometerChannelsCount - + + + + 3 + 1 + + TaskLogicParameters @@ -131,176 +109,55 @@ - OdorChannel - - - - Channel0 - - - - it ? 1.0 : 0.0 - it ? 1.0 : 0.0 - - - - - - - - OdorChannel - - - - Channel1 - - - - it ? 1.0 : 0.0 - it ? 1.0 : 0.0 + olfactometerChannelsCount - + - OdorChannel + thisOdor - - - Channel2 - - - - it ? 1.0 : 0.0 - it ? 1.0 : 0.0 + + OdorConfiguration.FlowRate - + - - true - 1 - 0 - 0 - NaN - true - 100 - 1000 + + 100 + 1000 + 3 - - ChannelsTargetFlow - - - OdorValveState - - - - - HarpOlfactometerCommands + SetOdorMix - + - - + + - + - + - - + + - - - - - - - - - - - - - - - - IsCarrierChannel? - - - - TaskLogicParameters - - - ChannelConfig - - - OdorChannel - - - - - - - - - Channel0 - - - - ChannelType - - - IsCarrier - - - - Source1 - - - - Carrier - - - - - - - - - - - - - - - - - - - - - - - false - - TaskLogicParameters @@ -308,7 +165,7 @@ TimeOff - + @@ -335,7 +192,7 @@ TimeOn - + @@ -375,9 +232,6 @@ true - - - 1 @@ -393,44 +247,35 @@ - - - - + + + - + - - + + - + - + - + - - + + - + - - - - - - - - @@ -450,47 +295,26 @@ Source1 - - Key - 1 - OdorChannel + thisOdor - TaskLogicParameters + thisOdor - - ChannelConfig + + CurrentOdorBeingCalibrated - OdorChannel - - - - - - - - - Channel0 - + thisOdor - - 1 - - - - ThisOdorChannel - - - - OdorChannel + + OdorChannel @@ -502,8 +326,8 @@ - - IsEndValveCalibration + + IsEndValveCalibration @@ -518,24 +342,25 @@ - Channel3AsCarrier + thisOdor - - - - + + OdorIndex - ThisOdorChannel - - - FlowRate + olfactometerChannelsCount - + + + + 3 + 0 + + TaskLogicParameters @@ -547,29 +372,39 @@ - - - true - 0 - 0 - 0 - NaN - true - 100 - 1000 - + + olfactometerChannelsCount - - ChannelsTargetFlow + + + + + + + thisOdor - OdorValveState + OdorConfiguration.FlowRate + + + + + - + + 100 + 1000 + 3 + - HarpOlfactometerCommands + SetOdorMix + + + + PT0.03S + @@ -583,88 +418,26 @@ - + - - + + - + - - - + + - + + + + - - IsCarrierChannel? - - - - TaskLogicParameters - - - ChannelConfig - - - OdorChannel - - - - - - - - - Channel0 - - - - ChannelType - - - IsCarrier - - - - Source1 - - - - Carrier - - - - - - - - - - - - - - - - - - - - - - - - - - false - - TaskLogicParameters @@ -672,7 +445,7 @@ TimeOff - + @@ -689,24 +462,25 @@ - Channel3AsCarrier + thisOdor - - - - + + OdorIndex - ThisOdorChannel - - - FlowRate + olfactometerChannelsCount - + + + + 3 + 1 + + TaskLogicParameters @@ -719,79 +493,33 @@ - OdorChannel - - - - Channel0 - - - - it ? 1.0 : 0.0 - it ? 1.0 : 0.0 + olfactometerChannelsCount - + - OdorChannel + thisOdor - - - Channel1 - - - - it ? 1.0 : 0.0 - it ? 1.0 : 0.0 - - - - - - - - OdorChannel - - - - Channel2 - - - - it ? 1.0 : 0.0 - it ? 1.0 : 0.0 + + OdorConfiguration.FlowRate - + - - true - 1 - 0 - 0 - NaN - true - 100 - 1000 + + 100 + 1000 + 3 - - ChannelsTargetFlow - - - OdorValveState - - - - - HarpOlfactometerCommands + SetOdorMix @@ -800,32 +528,21 @@ - + - - + + - + - + - - + + - - - - - - - - - - - - + @@ -836,7 +553,7 @@ TimeOn - + @@ -853,24 +570,25 @@ - Channel3AsCarrier + thisOdor - - - - + + OdorIndex - ThisOdorChannel - - - FlowRate + olfactometerChannelsCount - + + + + 3 + 0 + + TaskLogicParameters @@ -882,51 +600,53 @@ - - - true - 0 - 0 - 0 - NaN - true - 100 - 1000 - + + olfactometerChannelsCount - - ChannelsTargetFlow + + + + + + + thisOdor - OdorValveState + OdorConfiguration.FlowRate + + + + + - + + 100 + 1000 + 3 + - HarpOlfactometerCommands - - - + SetOdorMix - + - - + + - + - - - + + - + + @@ -954,9 +674,6 @@ true - - - 1 @@ -972,42 +689,33 @@ - - - + + - + - - + - - + + + - + - - + + - + - + - + - - - - - - - - diff --git a/src/Extensions/CreateOneHotConcentrationArray.cs b/src/Extensions/CreateOneHotConcentrationArray.cs new file mode 100644 index 0000000..88e884b --- /dev/null +++ b/src/Extensions/CreateOneHotConcentrationArray.cs @@ -0,0 +1,42 @@ +using Bonsai; +using System; +using System.ComponentModel; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using AindBehaviorDeviceOlfactometerDataSchema; + +[Combinator] +[Description("Creates an array of one-hot encoded concentration values for each olfactometer channel.")] +[WorkflowElementCategory(ElementCategory.Transform)] +public class CreateOneHotConcentrationArray +{ + private int channelNumber = 3; + public int ChannelNumber + { + get { return channelNumber; } + set { channelNumber = value; } + } + + private double concentration = 1.0; + public double Concentration + { + get { return concentration; } + set { concentration = value; } + } + + public IObservable> Process(IObservable source) + { + return source.Select(value => + { + var oneHotArray = new List(new double[channelNumber]); + oneHotArray[channelNumber] = concentration; + return oneHotArray; + }); + } + + public IObservable> Process(IObservable source) + { + return Process(source); + } +} diff --git a/src/Extensions/Hardware.bonsai b/src/Extensions/Hardware.bonsai index fc4f0b3..b40cef7 100644 --- a/src/Extensions/Hardware.bonsai +++ b/src/Extensions/Hardware.bonsai @@ -1,12 +1,11 @@  @@ -14,55 +13,33 @@ HarpDevices - - TaskLogicParameters - - - ChannelConfig - - - - 3 - - - - ChannelType - - - - Carrier - + + + true + Channel3AsCarrier - - Channel3AsCarrier - - - - - - RigSchema HarpOlfactometer + + RigSchema + - PortName + HarpOlfactometerExtension - - - - + + - - COM10 - TriggerHarpReadDump - true + + + RigSchema @@ -108,8 +85,8 @@ Event - - + + Seconds @@ -146,35 +123,31 @@ - - + - - - + + + - - + + - + - + - + + - - - - - + @@ -204,7 +177,7 @@ PoolingPeriod - + @@ -218,7 +191,7 @@ PT0.2S - + HarpTimestampSource diff --git a/src/Extensions/HardwareValidation.bonsai b/src/Extensions/HardwareValidation.bonsai index 1858b94..d4a9b9c 100644 --- a/src/Extensions/HardwareValidation.bonsai +++ b/src/Extensions/HardwareValidation.bonsai @@ -1,5 +1,5 @@  - - HarpOlfactometerEvents + OlfactometerHubEventsGroupedBy + + + it.Key == 0 + + + + + + Item2 @@ -118,14 +127,17 @@ - + - - - - - + + + + + + + + diff --git a/src/Extensions/HarpOlfactometerExtensionManager.bonsai b/src/Extensions/HarpOlfactometerExtensionManager.bonsai new file mode 100644 index 0000000..ebcce22 --- /dev/null +++ b/src/Extensions/HarpOlfactometerExtensionManager.bonsai @@ -0,0 +1,294 @@ + + + + + + Source1 + + + + + + InstantiateOlfactometer + + + + Source1 + + + + 1 + + + + thisOlfactometer + + + OlfactometerHubCommandsGroupedBy + + + thisOlfactometer + + + Index + + + + + + isThisOlfactometer? + it.Item1.Key == it.Item2 + + + + 1 + + + + Item1 + + + + + + Item2 + + + thisHarpOlfactometerCommands + + + thisHarpOlfactometerEvents + + + thisOlfactometer + + + Index + + + + + + Item2,Item1 + + + OlfactometerHubEvents + + + thisHarpOlfactometerCommands + + + thisOlfactometer + + + Value.PortName + + + + + + + + + Active + On + true + On + Enabled + false + COM6 + + + + thisHarpOlfactometerEvents + + + + + + TriggerHarpReadDump + + + Write + + Active + true + false + On + On + Enabled + + + + thisHarpOlfactometerCommands + + + thisOlfactometer + + + Index + + + Channel3Range + FlowRate1000 + + + Channel3Range + FlowRate100 + + + + + + it.Item1 == 0 ? it.Item2 : it.Item3 + + + Write + + + + thisHarpOlfactometerCommands + + + + PT0.05S + + + + Write + + Flowmeter DI0Trigger ChannelActualFlow + + + + thisHarpOlfactometerCommands + + + + PT0.05S + + + + Write + + Enabled + + + + thisHarpOlfactometerCommands + + + + PT0.05S + + + + Write + + None + + + + thisHarpOlfactometerCommands + + + thisHarpOlfactometerEvents + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MultiplexIO + + + + OlfactometerHubCommands + + + Item1 + + + OlfactometerHubCommandsGroupedBy + + + + OlfactometerHubEvents + + + Item1 + + + OlfactometerHubEventsGroupedBy + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Extensions/Logging.bonsai b/src/Extensions/Logging.bonsai index bb2b5c2..731a122 100644 --- a/src/Extensions/Logging.bonsai +++ b/src/Extensions/Logging.bonsai @@ -5,6 +5,7 @@ xmlns:p1="clr-namespace:AllenNeuralDynamics.Core;assembly=AllenNeuralDynamics.Core" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:p2="clr-namespace:Harp.Olfactometer;assembly=Harp.Olfactometer" + xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns:p3="clr-namespace:AllenNeuralDynamics.WhiteRabbit;assembly=AllenNeuralDynamics.WhiteRabbit" xmlns:p4="clr-namespace:Harp.AnalogInput;assembly=Harp.AnalogInput" xmlns:p5="clr-namespace:Harp.StepperDriver;assembly=Harp.StepperDriver" @@ -43,28 +44,88 @@ - HarpOlfactometerEvents + OlfactometerHubEventsGroupedBy - - Modality - - - - - + + LogOlfactometerHub + + + + Source1 + + + + 1 + + + + thisOlfactometer + + + + thisOlfactometer + + + + + + Item2 + + + Modality + + + + + + + + + + + + + + + + thisOlfactometer + + + Key + + + it > 0 ? "OlfactometerExtension" + it.ToString() : "Olfactometer" + + + + + + + + + Behavior + Olfactometer + + + + + + + + + + + + + + + + + + - - - - - - - - - - Behavior - Olfactometer + HarpWhiteRabbitEvents @@ -140,26 +201,23 @@ - + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + @@ -174,40 +232,108 @@ HarpSeconds - HarpOlfactometerCommands - - - HarpSeconds - - - - - - - - - - - - Modality + OlfactometerHubCommandsGroupedBy - - - - + + LogOlfactometerHub + + + + Source1 + + + + 1 + + + + thisOlfactometer + + + + thisOlfactometer + + + + + + Item2 + + + HarpTimestampSource + + + + + + + + + + + + Modality + + + + + + + + + + + + + + + + thisOlfactometer + + + Key + + + it > 0 ? "OlfactometerExtension" + it.ToString() : "Olfactometer" + + + HarpCommands/{0} + it + + + + + + + + + Behavior + Olfactometer + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - Behavior - HarpCommands/Olfactometer + HarpAnalogInputCommands @@ -320,38 +446,32 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Extensions/OdorControl.bonsai b/src/Extensions/OdorControl.bonsai new file mode 100644 index 0000000..e5ce572 --- /dev/null +++ b/src/Extensions/OdorControl.bonsai @@ -0,0 +1,527 @@ + + + + + + EndValveControl + + + + OlfactometerHubEvents + + + RigSchema + + + HarpOlfactometerExtension + + + Count + + + + 1 + + + + + + + + + + 1 + + + + + false + + + + OdorEndValveState + + + OdorEndValveState + + + OdorPath + + + + Source1 + + + + + + + + + + EndValvesState + EndValve0 + + + + CarrierPath + + + + Source1 + + + + + + + + + + EndValvesState + None + + + + + + Write + + + + + 0 + + + + + + + OlfactometerHubCommands + + + + + + + + + + + + + + + + + + + + + + + + + + + + InitialState + + + + OlfactometerHubEvents + + + RigSchema + + + HarpOlfactometerExtension + + + Count + + + + 1 + + + + + + + + + + 1 + + + + olfactometerChannelsCount + + + + + + + + + 0 + 0 + + + + + 0 + + + + + + + + + + Item2 + + + olfactometerChannelsCount + + + + + + + + TaskLogicParameters + + + FullFlowRate + + + + + + + + + 100 + 1000 + 3 + + + + SetOdorMix + + + + + + + + + + + + + + + + + + + + + + + + + + + + RigSchema + + + HarpOlfactometerExtension + + + 3 + 4*it.Count() + + + olfactometerChannelsCount + + + SetOdorMix + + + + + + BroadcastToOlfactometerHub + + + + Source1 + + + + 1 + + + + thisBundle + + + SetChannelsTargetFlow + + + + thisBundle + + + OlfactometerIndex,ChannelsTargetFlow + + + OlfactometerHubCommands + + + OlfactometerHubEventsGroupedBy + + + thisBundle + + + OlfactometerIndex + + + + + + it.Item1.Key == it.Item2 + + + Item1 + + + + + + Item2 + + + + Include + Write + + + + thisBundle + + + ChannelsTargetFlow + + + Address + + + + + + + + Include + + + + + + + + PT0.01S + + + + + + + + + 1 + + + + thisBundle + + + + + + Item2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SetOdorValveState + + + + thisBundle + + + OlfactometerIndex,OdorValveState + + + OlfactometerHubCommands + + + OlfactometerHubEventsGroupedBy + + + thisBundle + + + OlfactometerIndex + + + + + + it.Item1.Key == it.Item2 + + + Item1 + + + + + + Item2 + + + + Include + Write + + + + thisBundle + + + OdorValveState + + + Address + + + + + + + + Include + + + + + + + + PT0.01S + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Extensions/UserInterface.bonsai b/src/Extensions/UserInterface.bonsai index 62f4032..e9e2558 100644 --- a/src/Extensions/UserInterface.bonsai +++ b/src/Extensions/UserInterface.bonsai @@ -1,12 +1,12 @@  - - HarpAnalogInputEvents - - - - - - Seconds - Value.Channel0 - None - 3 - - - PID - Black - - - - - - - - - - - HarpOlfactometerEvents - - - - - - Seconds - Value - None - 1 - - - Channel0 - Blue - - - - - - - - - - - HarpOlfactometerEvents - - - - - - Seconds - Value - None - 1 - - - Channel1 - Blue - - - - - - - - - - - HarpOlfactometerEvents - - - - - - Seconds - Value - None - 1 - - - Channel2 - Blue - - - - - - - - - - - HarpOlfactometerEvents - - - - - - Seconds - Value - None - 1 - - - Blank - #C04000 - - - - - - - - - - - HarpOlfactometerEvents - - - - - - Seconds - Value - None - 1 - - - Carrier - Gray - - - - - - - - - - - HarpOlfactometerEvents - - - - - + + UserInterface - - Source1 + + HarpAnalogInputEvents + + + + + + Seconds + Value.Channel0 + None + 3 + + + PID + Black + + + + + + + + + + + CurrentOdorBeingCalibrated - - - EndValve0 - + + GetFlowFromCurrentChannel + + + + Source1 + + + + 1 + + + + thisOdor + + + thisOdor + + + OdorIndex + + + GetOlfactometerIndex + it < 3 ? 0 : ((it - 3) / 4 + 1) + + + thisOlfactometerIndex + + + thisOdor + + + OdorConfiguration.ChannelIndex + + + thisChannelIndex + + + + 48 + + + + thisChannelIndex + + + + + + + ActualFlowAddress + + + OlfactometerHubEventsGroupedBy + + + + + + Source1 + + + Key + + + thisOlfactometerIndex + + + + + + + + + + + + + + + + + + + 1 + + + + + + + Item2 + + + ActualFlowAddress + + + + + + + + + TimestampedFloat + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Seconds + Value + None + 1 + + + Blank + #C04000 + + + + + + + + + + + OlfactometerHubEventsGroupedBy + + + it.Key == 0 + + + + + + Item2 + + + + + + Seconds + Value + None + 1 + + + Blank + #C04000 + + + + + + + + + + + OlfactometerHubEventsGroupedBy + + + it.Key == 0 + + + + + + Item2 + + + + + + Seconds + Value + None + 1 + + + Carrier + Gray + + + + + + + + + + + OlfactometerHubEventsGroupedBy + + + it.Key == 0 + + + + + + Item2 + + + + + + + + + Source1 + + + + EndValve0 + + + + + + + + 1000 + + + + + + + Item1 ? Item2 : 0 + + + + + + + + + + + + + + + + Seconds + Value + Circle + 0 + + + EndValveState + Orange + + + + + + + + - + + + + Olfactometer + false + false + 10 + + X + Cluster + + + + + + + + SoftwareEvent + + + + + + + true + + + + + 1 + + + + StartExperimentTrigger + + + + false + - - 1000 + + 1 - + + + + + + + + + false + true + Microsoft Sans Serif, 16pt + Start! + + + StartButton + + + + + + CurrentOdorBeingCalibrated + + + CalibratingOdor {0} ({1}) + OdorIndex,OdorConfiguration + + + + + + + + false + true + Microsoft Sans Serif, 16pt + Start! - - Item1 ? Item2 : 0 + + + OlfactometerCalibration + true + true + 1 + 2 + + + + + + + OlfactometerCalibration + true + true + 2 + 1 + + + Percent + 0.8 + + + Percent + 0.2 + + + + + + + + OlfactometerCalibration + 1 + 2 + + + + Percent + 0.7 + + + Percent + 0.3 + + + + + + + + + + + + + AlicatFlowmeterEvents + + + Seconds + Value.VolumetricFlowRate,Value.MassFlowRate,Value.MassFlowTotal + None + 2 + + + EndValveState + Orange + + + 200 + + + + + + + + Seconds + Value.AbsolutePressure,Value.Temperature + None + 2 + + + EndValveState + Orange + + + 200 + + + + + + + + AlicatFlowmeter + true + true + 1 + 2 + + + + + + + + + UserInterface + true + true + + + OlfactometerCalibration + true + + + Minimized + + StartButton + + + + 1 + + + + + StartExperimentTrigger + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - Seconds - Value - Circle - 0 - - - EndValveState - Orange - - - - - - - - - - - - - - Olfactometer - false - false - 10 - - X - Cluster - - - - - - - - SoftwareEvent - - - - - - - true - - - - - 1 - - - - StartExperimentTrigger - - - - false - - - - - 1 - - - - - - - - - - - - false - true - Microsoft Sans Serif, 16pt - Start! - - - StartButton - - - - - - OlfactometerCalibration - true - true - 2 - 1 - - - Percent - 0.8 - - - Percent - 0.2 - - - - - - - - OlfactometerCalibration - 1 - 2 - - - - Percent - 0.7 - - - Percent - 0.3 - - - - - - - - - - - - - AlicatFlowmeterEvents - - - Seconds - Value.VolumetricFlowRate,Value.MassFlowRate,Value.MassFlowTotal - None - 2 - - - EndValveState - Orange - - - 200 - - - - - - - - Seconds - Value.AbsolutePressure,Value.Temperature - None - 2 - - - EndValveState - Orange - - - 200 - - - - - - - - AlicatFlowmeter - true - true - 1 - 2 - - - - - - - - - UserInterface - true - true - - - OlfactometerCalibration - true - - - Minimized - - - StartButton - - - - 1 - - - - - StartExperimentTrigger - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/aind_behavior_device_olfactometer/task_logic.py b/src/aind_behavior_device_olfactometer/task_logic.py index 2b81577..d200ed6 100644 --- a/src/aind_behavior_device_olfactometer/task_logic.py +++ b/src/aind_behavior_device_olfactometer/task_logic.py @@ -1,15 +1,18 @@ -from typing import Dict, Literal +from typing import Dict, List, Literal, Tuple from aind_behavior_services.rig import olfactometer as olf from aind_behavior_services.task import Task, TaskParameters -from pydantic import Field +from pydantic import Field, BaseModel from . import __semver__ - +class ChannelToCalibrate(BaseModel): + odor_index: int = Field(title="Olfactometer odor channel index. This is an absolute count across all olfactometer channels") + odor_configuration: olf.OlfactometerChannelConfig = Field(title="Olfactometer channel configuration") + class OlfactometerCalibrationParameters(TaskParameters): - channel_config: Dict[olf.OlfactometerChannel, olf.OlfactometerChannelConfig] = Field( - default_factory=dict, description="Configuration of olfactometer channels" + channel_config: List[ChannelToCalibrate] = Field( + default_factory=list, description="List of olfactometer channels to calibrate with their configurations" ) full_flow_rate: float = Field(default=1000, ge=0, le=1000, description="Full flow rate of the olfactometer") n_repeats_per_stimulus: int = Field(default=1, ge=1, description="Number of repeats per stimulus") diff --git a/src/main.bonsai b/src/main.bonsai index e324140..dcd113a 100644 --- a/src/main.bonsai +++ b/src/main.bonsai @@ -2,12 +2,11 @@ @@ -22,9 +21,7 @@ C:\git\AllenNeuralDynamics\Aind.Behavior.Device.Olfactometer\local\olfactometer_session.json - - 10 - + @@ -50,171 +47,7 @@ - - OdorControl - - - - EndValveControl - - - - HarpOlfactometerEvents - - - - 1 - - - - - false - - - - OdorEndValveState - - - OdorEndValveState - - - OdorPath - - - - Source1 - - - - - - - - - - EndValvesState - EndValve0 - - - - CarrierPath - - - - Source1 - - - - - - - - - - EndValvesState - None - - - - - - Write - - - - HarpOlfactometerCommands - - - - - - - - - - - - - - - - - - - - InitialState - - - - Channel3AsCarrier - - - - - - - - TaskLogicParameters - - - FullFlowRate - - - - - - - - - true - 0 - 0 - 0 - NaN - true - 100 - 1000 - - - - ChannelsTargetFlow - - - OdorValveState - - - - - - HarpOlfactometerCommands - - - HarpOlfactometerEvents - - - - - - - - - - - - - - - - - - - - - - - - - + Init @@ -244,16 +77,19 @@ LoggingRootPath - - ../. + + ../. Repository - + SoftwareEvent + + CurrentOdorBeingCalibrated + @@ -303,7 +139,7 @@ - + StartExperimentTrigger @@ -333,36 +169,38 @@ - - true - 0 - 0 - 0 - NaN - true - 100 - 1000 + + 0 - - - 1 - + + olfactometerChannelsCount - - ChannelsTargetFlow + + + + - - OdorValveState + + + 3 + 0 + - + + 100 + 1000 + 3 + - HarpOlfactometerCommands + SetOdorMix - + + PT0.05S + @@ -375,11 +213,10 @@ - + - - - + + From 8e45409f107f796cd77e6ada5f7185985d5ae51b Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Tue, 21 Apr 2026 11:55:13 -0700 Subject: [PATCH 04/10] Add qc and data contract --- .../data_contract/__init__.py | 2 + .../data_contract/v0_3_0.py | 21 +- .../data_contract/v0_4_0.py | 194 ++++++++++++++++++ .../data_qc/data_qc.py | 20 +- .../task_logic.py | 12 +- 5 files changed, 230 insertions(+), 19 deletions(-) create mode 100644 src/aind_behavior_device_olfactometer/data_contract/v0_4_0.py diff --git a/src/aind_behavior_device_olfactometer/data_contract/__init__.py b/src/aind_behavior_device_olfactometer/data_contract/__init__.py index af54785..32e73f2 100644 --- a/src/aind_behavior_device_olfactometer/data_contract/__init__.py +++ b/src/aind_behavior_device_olfactometer/data_contract/__init__.py @@ -13,6 +13,8 @@ def _dataset_lookup_helper(version: str) -> t.Callable[[Path], contraqctor.contr parsed_version = semver.Version.parse(version) if semver.Version.parse("0.3.0") <= parsed_version < semver.Version.parse("0.4.0"): from .v0_3_0 import dataset as _dataset + elif semver.Version.parse("0.4.0") <= parsed_version: + from .v0_4_0 import dataset as _dataset else: raise ValueError(f"Unsupported version: {version}") return partial(_dataset, version=version) diff --git a/src/aind_behavior_device_olfactometer/data_contract/v0_3_0.py b/src/aind_behavior_device_olfactometer/data_contract/v0_3_0.py index 2dd8328..05e86fb 100644 --- a/src/aind_behavior_device_olfactometer/data_contract/v0_3_0.py +++ b/src/aind_behavior_device_olfactometer/data_contract/v0_3_0.py @@ -1,17 +1,13 @@ from pathlib import Path -from aind_behavior_services.session import AindBehaviorSessionModel from contraqctor.contract import Dataset, DataStreamCollection from contraqctor.contract.harp import ( DeviceYmlByFile, HarpDevice, ) -from contraqctor.contract.json import PydanticModel, SoftwareEvents +from contraqctor.contract.json import Json, SoftwareEvents from contraqctor.contract.text import Text -from ..rig import OlfactometerCalibrationRig -from ..task_logic import OlfactometerCalibrationLogic - def dataset( root_path: Path, @@ -138,24 +134,21 @@ def dataset( name="InputSchemas", description="Configuration files for the behavior rig, task_logic and session.", data_streams=[ - PydanticModel( + Json( name="Rig", - reader_params=PydanticModel.make_params( - model=OlfactometerCalibrationRig, + reader_params=Json.make_params( path=root_path / "behavior/Logs/rig_input.json", ), ), - PydanticModel( + Json( name="TaskLogic", - reader_params=PydanticModel.make_params( - model=OlfactometerCalibrationLogic, + reader_params=Json.make_params( path=root_path / "behavior/Logs/tasklogic_input.json", ), ), - PydanticModel( + Json( name="Session", - reader_params=PydanticModel.make_params( - model=AindBehaviorSessionModel, + reader_params=Json.make_params( path=root_path / "behavior/Logs/session_input.json", ), ), diff --git a/src/aind_behavior_device_olfactometer/data_contract/v0_4_0.py b/src/aind_behavior_device_olfactometer/data_contract/v0_4_0.py new file mode 100644 index 0000000..7675165 --- /dev/null +++ b/src/aind_behavior_device_olfactometer/data_contract/v0_4_0.py @@ -0,0 +1,194 @@ +from pathlib import Path + +from aind_behavior_services.session import Session +from contraqctor.contract import Dataset, DataStreamCollection +from contraqctor.contract.harp import ( + DeviceYmlByFile, + HarpDevice, +) +from contraqctor.contract.json import PydanticModel, SoftwareEvents +from contraqctor.contract.mux import MapFromPaths +from contraqctor.contract.text import Text + +from ..rig import OlfactometerCalibrationRig +from ..task_logic import OlfactometerCalibrationLogic + + +def dataset( + root_path: Path, + name: str = "OlfactometerCalibrationDataset", + description: str = "A Olfactometer Calibration dataset", + version: str = "0.4.0", +) -> Dataset: + """ + Creates a Dataset object for the Olfactometer calibration procedure. + """ + + root_path = Path(root_path) + return Dataset( + name=name, + version=version, + description=description, + data_streams=[ + DataStreamCollection( + name="Behavior", + description="Data from the Behavior modality", + data_streams=[ + HarpDevice( + name="HarpOlfactometer", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/Olfactometer.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + HarpDevice( + name="HarpClockGenerator", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/WhiteRabbit.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + HarpDevice( + name="HarpAnalogInput", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/AnalogInput.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + HarpDevice( + name="HarpManipulator", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/StepperDriver.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + MapFromPaths( + name="HarpOlfactometerExtension", + description="Data from any additional Harp Olfactometer devices that were added as extensions to the main olfactometer. The number of these devices can vary between sessions, but they will always be named sequentially as OlfactometerExtension1, OlfactometerExtension2, etc.", + reader_params=MapFromPaths.make_params( + paths=[root_path / "behavior"], + include_glob_pattern=["OlfactometerExtension*.harp"], + inner_data_stream=HarpDevice, + inner_param_factory=lambda device_name: HarpDevice.make_params( + path=root_path / "behavior" / device_name, + device_yml_hint=DeviceYmlByFile(), + ), + ), + ), + DataStreamCollection( + name="HarpCommands", + description="Commands sent to Harp devices", + data_streams=[ + HarpDevice( + name="HarpOlfactometer", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/HarpCommands/Olfactometer.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + HarpDevice( + name="HarpManipulator", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/HarpCommands/StepperDriver.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + HarpDevice( + name="HarpClockGenerator", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/HarpCommands/WhiteRabbit.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + HarpDevice( + name="HarpAnalogInput", + reader_params=HarpDevice.make_params( + path=root_path / "behavior/HarpCommands/AnalogInput.harp", + device_yml_hint=DeviceYmlByFile(), + ), + ), + MapFromPaths( + name="HarpOlfactometerExtension", + description="Data from any additional Harp Olfactometer devices that were added as extensions to the main olfactometer. The number of these devices can vary between sessions, but they will always be named sequentially as OlfactometerExtension1, OlfactometerExtension2, etc.", + reader_params=MapFromPaths.make_params( + paths=[root_path / "behavior/HarpCommands"], + include_glob_pattern=["OlfactometerExtension*.harp"], + inner_data_stream=HarpDevice, + inner_param_factory=lambda device_name: HarpDevice.make_params( + path=root_path / "behavior/HarpCommands" / device_name, + device_yml_hint=DeviceYmlByFile(), + ), + ), + ), + ], + ), + DataStreamCollection( + name="SoftwareEvents", + description="Software events generated by the workflow. The timestamps of these events are low precision and should not be used to align to physiology data.", + data_streams=[ + SoftwareEvents( + name="OdorChannel", + description="The OdorChannel currently being used.", + reader_params=SoftwareEvents.make_params( + root_path / "behavior/SoftwareEvents/OdorChannel.json" + ), + ), + SoftwareEvents( + name="IsEndValveCalibration", + description="Whether the calibration is running in end-valve mode or not.", + reader_params=SoftwareEvents.make_params( + root_path / "behavior/SoftwareEvents/IsEndValveCalibration.json" + ), + ), + ], + ), + DataStreamCollection( + name="Logs", + data_streams=[ + Text( + name="Launcher", + description="Contains the console log of the launcher process.", + reader_params=Text.make_params( + path=root_path / "behavior/Logs/.launcher/launcher.log", + ), + ), + SoftwareEvents( + name="EndSession", + description="A file that determines the end of the session. If the file is empty, the session is still running or it was not closed properly.", + reader_params=SoftwareEvents.make_params( + path=root_path / "behavior/SoftwareEvents/EndSession.json", + ), + ), + ], + ), + DataStreamCollection( + name="InputSchemas", + description="Configuration files for the behavior rig, task_logic and session.", + data_streams=[ + PydanticModel( + name="Rig", + reader_params=PydanticModel.make_params( + model=OlfactometerCalibrationRig, + path=root_path / "behavior/Logs/rig_input.json", + ), + ), + PydanticModel( + name="TaskLogic", + reader_params=PydanticModel.make_params( + model=OlfactometerCalibrationLogic, + path=root_path / "behavior/Logs/tasklogic_input.json", + ), + ), + PydanticModel( + name="Session", + reader_params=PydanticModel.make_params( + model=Session, + path=root_path / "behavior/Logs/session_input.json", + ), + ), + ], + ), + ], + ), + ], + ) diff --git a/src/aind_behavior_device_olfactometer/data_qc/data_qc.py b/src/aind_behavior_device_olfactometer/data_qc/data_qc.py index 8935868..90a8fbd 100644 --- a/src/aind_behavior_device_olfactometer/data_qc/data_qc.py +++ b/src/aind_behavior_device_olfactometer/data_qc/data_qc.py @@ -71,14 +71,32 @@ def make_qc_runner(dataset: contract.Dataset) -> qc.Runner: commands = t.cast(HarpDevice, _r["HarpCommands"][stream.name]) _runner.add_suite(qc.harp.HarpDeviceTestSuite(stream, commands), stream.name) + # Also add the HarpOlfactometerExtension if it exists, as it may not be present in all sessions and is not a HarpDevice itself but contains them + dataset["Behavior"]["HarpOlfactometerExtension"].load() + dataset["Behavior"]["HarpCommands"]["HarpOlfactometerExtension"].load() + + for stream in dataset["Behavior"]["HarpOlfactometerExtension"]: + if isinstance(stream, HarpDevice): + commands = t.cast(HarpDevice, dataset["Behavior"]["HarpCommands"]["HarpOlfactometerExtension"][stream.name]) + _runner.add_suite(qc.harp.HarpDeviceTestSuite(stream, commands), stream.name) + # Add Harp Hub tests + all_harp_devices = [harp_device for harp_device in dataset["Behavior"] if isinstance(harp_device, HarpDevice)] + all_harp_devices.extend( + [ + harp_device + for harp_device in dataset["Behavior"]["HarpOlfactometerExtension"] + if isinstance(harp_device, HarpDevice) + ] + ) _runner.add_suite( qc.harp.HarpHubTestSuite( dataset["Behavior"]["HarpClockGenerator"], - [harp_device for harp_device in dataset["Behavior"] if isinstance(harp_device, HarpDevice)], + all_harp_devices, ), "HarpHub", ) + # Add Csv tests csv_streams = [stream for stream in dataset.iter_all() if isinstance(stream, contract.csv.Csv)] for stream in csv_streams: diff --git a/src/aind_behavior_device_olfactometer/task_logic.py b/src/aind_behavior_device_olfactometer/task_logic.py index d200ed6..569af6b 100644 --- a/src/aind_behavior_device_olfactometer/task_logic.py +++ b/src/aind_behavior_device_olfactometer/task_logic.py @@ -1,15 +1,19 @@ -from typing import Dict, List, Literal, Tuple +from typing import List, Literal from aind_behavior_services.rig import olfactometer as olf from aind_behavior_services.task import Task, TaskParameters -from pydantic import Field, BaseModel +from pydantic import BaseModel, Field from . import __semver__ + class ChannelToCalibrate(BaseModel): - odor_index: int = Field(title="Olfactometer odor channel index. This is an absolute count across all olfactometer channels") + odor_index: int = Field( + title="Olfactometer odor channel index. This is an absolute count across all olfactometer channels" + ) odor_configuration: olf.OlfactometerChannelConfig = Field(title="Olfactometer channel configuration") - + + class OlfactometerCalibrationParameters(TaskParameters): channel_config: List[ChannelToCalibrate] = Field( default_factory=list, description="List of olfactometer channels to calibrate with their configurations" From 1f6ea677226d4cfc2a1aac6c84d785aa6efbec3c Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:26:18 -0700 Subject: [PATCH 05/10] Fix example --- examples/clabe.yml | 6 ------ examples/olfactometer.py | 37 ++++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) delete mode 100644 examples/clabe.yml diff --git a/examples/clabe.yml b/examples/clabe.yml deleted file mode 100644 index dcf0b5e..0000000 --- a/examples/clabe.yml +++ /dev/null @@ -1,6 +0,0 @@ -# In order to get picked up, move this file to the root of the project or ./local - -data_dir: C:/Data - -default_behavior_picker: - config_library_dir: '\\allen\aind\scratch\AindBehavior.db\AindBehaviorDeviceOlfactometer' diff --git a/examples/olfactometer.py b/examples/olfactometer.py index b65968b..6a89444 100644 --- a/examples/olfactometer.py +++ b/examples/olfactometer.py @@ -9,27 +9,31 @@ from aind_behavior_device_olfactometer import rig, task_logic from aind_behavior_device_olfactometer.rig import AlicatFlowmeter -olf_calibration = olf.OlfactometerCalibration( - channel_config={ - olf.OlfactometerChannel.Channel0: olf.OlfactometerChannelConfig( +channels2calibrate = [ + task_logic.ChannelToCalibrate( + odor_index=0, + odor_configuration=olf.OlfactometerChannelConfig( channel_index=olf.OlfactometerChannel.Channel0, channel_type=olf.OlfactometerChannelType.ODOR, flow_rate=100, odorant="Banana", odorant_dilution=0.1, ), - olf.OlfactometerChannel.Channel3: olf.OlfactometerChannelConfig( + ), + task_logic.ChannelToCalibrate( + odor_index=3, + odor_configuration=olf.OlfactometerChannelConfig( channel_index=olf.OlfactometerChannel.Channel3, channel_type=olf.OlfactometerChannelType.CARRIER, odorant="Air", ), - } -) + ), +] calibration_logic = task_logic.OlfactometerCalibrationLogic( task_parameters=task_logic.OlfactometerCalibrationParameters( - channel_config=olf_calibration.channel_config, + channel_config=channels2calibrate, full_flow_rate=1000, n_repeats_per_stimulus=10, time_on=2, @@ -57,6 +61,25 @@ initial_position=man.ManipulatorPosition(y1=0, y2=0, x=0, z=0), ) + +olf_calibration = olf.OlfactometerCalibration( + channel_config={ + olf.OlfactometerChannel.Channel0: olf.OlfactometerChannelConfig( + channel_index=olf.OlfactometerChannel.Channel0, + channel_type=olf.OlfactometerChannelType.ODOR, + flow_rate=100, + odorant="Banana", + odorant_dilution=0.1, + ), + olf.OlfactometerChannel.Channel3: olf.OlfactometerChannelConfig( + channel_index=olf.OlfactometerChannel.Channel3, + channel_type=olf.OlfactometerChannelType.CARRIER, + odorant="Air", + ), + } +) + + _rig = rig.OlfactometerCalibrationRig( computer_name="TestPC", data_directory="c:/data", From 9c0c19973b7e62e2b63dcfc8054524c3d70a2b6b Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:42:31 -0700 Subject: [PATCH 06/10] Refactor package to simplify task logic and odor definition --- examples/olfactometer.py | 14 ++ schema/aind_behavior_device_olfactometer.json | 26 --- scripts/aind_launcher.py | 46 +--- ...indBehaviorDeviceOlfactometer.Generated.cs | 109 --------- src/Extensions/CalibrationLogic.bonsai | 212 ++++++++++-------- .../CreateOneHotConcentrationArray.cs | 29 +-- src/Extensions/OdorControl.bonsai | 6 +- src/Extensions/UserInterface.bonsai | 21 +- src/aind_behavior_device_olfactometer/rig.py | 6 +- .../task_logic.py | 15 +- src/main.bonsai | 49 ++-- uv.lock | 53 ++++- 12 files changed, 266 insertions(+), 320 deletions(-) diff --git a/examples/olfactometer.py b/examples/olfactometer.py index 6a89444..cb15d20 100644 --- a/examples/olfactometer.py +++ b/examples/olfactometer.py @@ -71,6 +71,20 @@ odorant="Banana", odorant_dilution=0.1, ), + olf.OlfactometerChannel.Channel1: olf.OlfactometerChannelConfig( + channel_index=olf.OlfactometerChannel.Channel1, + channel_type=olf.OlfactometerChannelType.ODOR, + flow_rate=100, + odorant="None", + odorant_dilution=0.0, + ), + olf.OlfactometerChannel.Channel2: olf.OlfactometerChannelConfig( + channel_index=olf.OlfactometerChannel.Channel2, + channel_type=olf.OlfactometerChannelType.ODOR, + flow_rate=100, + odorant="None", + odorant_dilution=0.0, + ), olf.OlfactometerChannel.Channel3: olf.OlfactometerChannelConfig( channel_index=olf.OlfactometerChannel.Channel3, channel_type=olf.OlfactometerChannelType.CARRIER, diff --git a/schema/aind_behavior_device_olfactometer.json b/schema/aind_behavior_device_olfactometer.json index cdc7f9e..498bf6f 100644 --- a/schema/aind_behavior_device_olfactometer.json +++ b/schema/aind_behavior_device_olfactometer.json @@ -331,24 +331,6 @@ "title": "BaseModel", "type": "object" }, - "ChannelToCalibrate": { - "properties": { - "odor_index": { - "title": "Olfactometer odor channel index. This is an absolute count across all olfactometer channels", - "type": "integer" - }, - "odor_configuration": { - "$ref": "#/$defs/OlfactometerChannelConfig", - "title": "Olfactometer channel configuration" - } - }, - "required": [ - "odor_index", - "odor_configuration" - ], - "title": "ChannelToCalibrate", - "type": "object" - }, "ConnectedClockOutput": { "properties": { "target_device": { @@ -698,14 +680,6 @@ "title": "aind_behavior_services package version", "type": "string" }, - "channel_config": { - "description": "List of olfactometer channels to calibrate with their configurations", - "items": { - "$ref": "#/$defs/ChannelToCalibrate" - }, - "title": "Channel Config", - "type": "array" - }, "full_flow_rate": { "default": 1000, "description": "Full flow rate of the olfactometer", diff --git a/scripts/aind_launcher.py b/scripts/aind_launcher.py index ae41fe8..d552aae 100644 --- a/scripts/aind_launcher.py +++ b/scripts/aind_launcher.py @@ -12,7 +12,7 @@ from pydantic_settings import CliApp from aind_behavior_device_olfactometer.rig import OlfactometerCalibrationRig -from aind_behavior_device_olfactometer.task_logic import OlfactometerCalibrationLogic +from aind_behavior_device_olfactometer.task_logic import OlfactometerCalibrationLogic, OlfactometerCalibrationParameters logger = logging.getLogger(__name__) @@ -29,9 +29,16 @@ async def calibration_experiment(launcher: Launcher) -> None: ) session = picker.pick_session(Session) - task_logic = picker.pick_task(OlfactometerCalibrationLogic) + + task_logic = OlfactometerCalibrationLogic(task_parameters=OlfactometerCalibrationParameters()) + launcher.ui_helper.print(f"Loading task logic with default parameters: {task_logic.task_parameters}") + skip_manual_task = launcher.ui_helper.prompt_yes_no_question( + "Skip manual task parameter configuration and use defaults?" + ) + if not skip_manual_task: + task_logic = picker.pick_task(OlfactometerCalibrationLogic) + rig = picker.pick_rig(OlfactometerCalibrationRig) - ensure_rig_and_computer_name(rig) launcher.register_session(session, rig.data_directory) @@ -67,7 +74,7 @@ async def calibration_experiment(launcher: Launcher) -> None: runner.run_all_with_progress(reporter=reporter) webbrowser.open(qc_path.as_uri(), new=2) except Exception as e: - logger.error(f"Failed to run data QC: {e}") + logger.error("Failed to run data QC: %s", e) # Transfer data is_transfer = picker.ui_helper.prompt_yes_no_question("Would you like to transfer data?") @@ -80,37 +87,6 @@ async def calibration_experiment(launcher: Launcher) -> None: return -def ensure_rig_and_computer_name(rig: OlfactometerCalibrationRig) -> None: - """Ensures rig and computer name are set from environment variables if available, otherwise defaults to rig configuration values.""" - - import os - - rig_name = os.environ.get("aibs_comp_id", None) - computer_name = os.environ.get("hostname", None) - - if rig_name is None: - logger.warning( - "'aibs_comp_id' environment variable not set. Defaulting to rig name from configuration. %s", rig.rig_name - ) - rig_name = rig.rig_name - if computer_name is None: - computer_name = rig.computer_name - logger.warning( - "'hostname' environment variable not set. Defaulting to computer name from configuration. %s", - rig.computer_name, - ) - - if rig_name != rig.rig_name or computer_name != rig.computer_name: - logger.warning( - "Rig name or computer name from environment variables do not match the rig configuration. " - "Forcing rig name: %s and computer name: %s from environment variables.", - rig_name, - computer_name, - ) - rig.rig_name = rig_name - rig.computer_name = computer_name - - class ClabeCli(LauncherCliArgs): def cli_cmd(self): launcher = Launcher(settings=self) diff --git a/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs b/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs index 642cc10..8c86af0 100644 --- a/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs +++ b/src/Extensions/AindBehaviorDeviceOlfactometer.Generated.cs @@ -202,86 +202,6 @@ public override string ToString() } - [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] - [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] - [Bonsai.CombinatorAttribute(MethodName="Generate")] - public partial class ChannelToCalibrate - { - - private int _odorIndex; - - private OlfactometerChannelConfig _odorConfiguration; - - public ChannelToCalibrate() - { - _odorConfiguration = new OlfactometerChannelConfig(); - } - - protected ChannelToCalibrate(ChannelToCalibrate other) - { - _odorIndex = other._odorIndex; - _odorConfiguration = other._odorConfiguration; - } - - [Newtonsoft.Json.JsonPropertyAttribute("odor_index", Required=Newtonsoft.Json.Required.Always)] - public int OdorIndex - { - get - { - return _odorIndex; - } - set - { - _odorIndex = value; - } - } - - [System.Xml.Serialization.XmlIgnoreAttribute()] - [Newtonsoft.Json.JsonPropertyAttribute("odor_configuration", Required=Newtonsoft.Json.Required.Always)] - public OlfactometerChannelConfig OdorConfiguration - { - get - { - return _odorConfiguration; - } - set - { - _odorConfiguration = value; - } - } - - public System.IObservable Generate() - { - return System.Reactive.Linq.Observable.Defer(() => System.Reactive.Linq.Observable.Return(new ChannelToCalibrate(this))); - } - - public System.IObservable Generate(System.IObservable source) - { - return System.Reactive.Linq.Observable.Select(source, _ => new ChannelToCalibrate(this)); - } - - protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) - { - stringBuilder.Append("OdorIndex = " + _odorIndex + ", "); - stringBuilder.Append("OdorConfiguration = " + _odorConfiguration); - return true; - } - - public override string ToString() - { - System.Text.StringBuilder stringBuilder = new System.Text.StringBuilder(); - stringBuilder.Append(GetType().Name); - stringBuilder.Append(" { "); - if (PrintMembers(stringBuilder)) - { - stringBuilder.Append(" "); - } - stringBuilder.Append("}"); - return stringBuilder.ToString(); - } - } - - [System.CodeDom.Compiler.GeneratedCodeAttribute("Bonsai.Sgen", "0.9.0.0 (Newtonsoft.Json v13.0.0.0)")] [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Source)] [Bonsai.CombinatorAttribute(MethodName="Generate")] @@ -1074,8 +994,6 @@ public partial class OlfactometerCalibrationParameters private string _aindBehaviorServicesPkgVersion; - private System.Collections.Generic.List _channelConfig; - private double _fullFlowRate; private int _nRepeatsPerStimulus; @@ -1087,7 +1005,6 @@ public partial class OlfactometerCalibrationParameters public OlfactometerCalibrationParameters() { _aindBehaviorServicesPkgVersion = "0.13.7"; - _channelConfig = new System.Collections.Generic.List(); _fullFlowRate = 1000D; _nRepeatsPerStimulus = 1; _timeOn = 1D; @@ -1098,7 +1015,6 @@ protected OlfactometerCalibrationParameters(OlfactometerCalibrationParameters ot { _rngSeed = other._rngSeed; _aindBehaviorServicesPkgVersion = other._aindBehaviorServicesPkgVersion; - _channelConfig = other._channelConfig; _fullFlowRate = other._fullFlowRate; _nRepeatsPerStimulus = other._nRepeatsPerStimulus; _timeOn = other._timeOn; @@ -1135,24 +1051,6 @@ public string AindBehaviorServicesPkgVersion } } - /// - /// List of olfactometer channels to calibrate with their configurations - /// - [System.Xml.Serialization.XmlIgnoreAttribute()] - [Newtonsoft.Json.JsonPropertyAttribute("channel_config")] - [System.ComponentModel.DescriptionAttribute("List of olfactometer channels to calibrate with their configurations")] - public System.Collections.Generic.List ChannelConfig - { - get - { - return _channelConfig; - } - set - { - _channelConfig = value; - } - } - /// /// Full flow rate of the olfactometer /// @@ -1235,7 +1133,6 @@ protected virtual bool PrintMembers(System.Text.StringBuilder stringBuilder) { stringBuilder.Append("RngSeed = " + _rngSeed + ", "); stringBuilder.Append("AindBehaviorServicesPkgVersion = " + _aindBehaviorServicesPkgVersion + ", "); - stringBuilder.Append("ChannelConfig = " + _channelConfig + ", "); stringBuilder.Append("FullFlowRate = " + _fullFlowRate + ", "); stringBuilder.Append("NRepeatsPerStimulus = " + _nRepeatsPerStimulus + ", "); stringBuilder.Append("TimeOn = " + _timeOn + ", "); @@ -2049,11 +1946,6 @@ public System.IObservable Process(System.IObservable source) return Process(source); } - public System.IObservable Process(System.IObservable source) - { - return Process(source); - } - public System.IObservable Process(System.IObservable source) { return Process(source); @@ -2115,7 +2007,6 @@ public System.IObservable Process(System.IObservable source) [Bonsai.WorkflowElementCategoryAttribute(Bonsai.ElementCategory.Transform)] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] - [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] [System.Xml.Serialization.XmlIncludeAttribute(typeof(Bonsai.Expressions.TypeMapping))] diff --git a/src/Extensions/CalibrationLogic.bonsai b/src/Extensions/CalibrationLogic.bonsai index 6c49849..9e671ec 100644 --- a/src/Extensions/CalibrationLogic.bonsai +++ b/src/Extensions/CalibrationLogic.bonsai @@ -12,10 +12,49 @@ - TaskLogicParameters + RigSchema - ChannelConfig + HarpOlfactometer + + + RigSchema + + + HarpOlfactometerExtension + + + + + + + + + + + + Value.Calibration.ChannelConfig + + + + + + Value + + + + + + Item2.Index,Item1 + + + + + + ChannelsToCalibrate + + + ChannelsToCalibrate @@ -80,20 +119,17 @@ thisOdor - - OdorIndex - - olfactometerChannelsCount + olfactometesCount - + - 3 + 12 1 @@ -109,7 +145,7 @@ - olfactometerChannelsCount + olfactometesCount @@ -120,7 +156,7 @@ thisOdor - OdorConfiguration.FlowRate + Item2.FlowRate @@ -140,21 +176,20 @@ - - - - - + + + + + - - - - + + + + - - + + - @@ -280,10 +315,7 @@ - TaskLogicParameters - - - ChannelConfig + ChannelsToCalibrate @@ -344,20 +376,17 @@ thisOdor - - OdorIndex - - olfactometerChannelsCount + olfactometesCount - + - 3 + 12 0 @@ -373,7 +402,7 @@ - olfactometerChannelsCount + olfactometesCount @@ -384,7 +413,7 @@ thisOdor - OdorConfiguration.FlowRate + Item2.FlowRate @@ -417,24 +446,23 @@ - - - - - + + + + + - - - - + + + + - - + + - @@ -464,20 +492,17 @@ thisOdor - - OdorIndex - - olfactometerChannelsCount + olfactometesCount - + - 3 + 12 1 @@ -493,7 +518,7 @@ - olfactometerChannelsCount + olfactometesCount @@ -504,7 +529,7 @@ thisOdor - OdorConfiguration.FlowRate + Item2.FlowRate @@ -527,22 +552,21 @@ - - - - - + + + + + - - - - + + + + - - + + - @@ -572,20 +596,17 @@ thisOdor - - OdorIndex - - olfactometerChannelsCount + olfactometesCount - + - 3 + 12 0 @@ -601,7 +622,7 @@ - olfactometerChannelsCount + olfactometesCount @@ -612,7 +633,7 @@ thisOdor - OdorConfiguration.FlowRate + Item2.FlowRate @@ -632,21 +653,20 @@ - - - - - + + + + + - - - - + + + + - - + + - @@ -732,16 +752,28 @@ - + - - + + - + + + + + + + + + + + + + diff --git a/src/Extensions/CreateOneHotConcentrationArray.cs b/src/Extensions/CreateOneHotConcentrationArray.cs index 88e884b..30d1401 100644 --- a/src/Extensions/CreateOneHotConcentrationArray.cs +++ b/src/Extensions/CreateOneHotConcentrationArray.cs @@ -11,11 +11,11 @@ [WorkflowElementCategory(ElementCategory.Transform)] public class CreateOneHotConcentrationArray { - private int channelNumber = 3; - public int ChannelNumber + private int olfactometerCount = 12; + public int OlfactometerCount { - get { return channelNumber; } - set { channelNumber = value; } + get { return olfactometerCount; } + set { olfactometerCount = value; } } private double concentration = 1.0; @@ -24,19 +24,22 @@ public double Concentration get { return concentration; } set { concentration = value; } } - - public IObservable> Process(IObservable source) + + public IObservable> Process(IObservable> source) { return source.Select(value => { - var oneHotArray = new List(new double[channelNumber]); - oneHotArray[channelNumber] = concentration; + var olfactometerIndex = value.Item1; + var channelConfig = value.Item2; + var numberOfChannels = 3 + (olfactometerCount-1) * 4; // 3 channels for the first olfactometer, 4 channels for each additional olfactometer + var oneHotArray = new List(new double[numberOfChannels]); + var globalChannelIndex = olfactometerIndex == 0 ? channelConfig.ChannelIndex : (3 + olfactometerIndex * 4) + channelConfig.ChannelIndex; + if (globalChannelIndex >= oneHotArray.Count) + { + throw new ArgumentOutOfRangeException("Calculated global channel index " + globalChannelIndex + " exceeds the one-hot array size."); + } + oneHotArray[globalChannelIndex] = concentration; return oneHotArray; }); } - - public IObservable> Process(IObservable source) - { - return Process(source); - } } diff --git a/src/Extensions/OdorControl.bonsai b/src/Extensions/OdorControl.bonsai index e5ce572..970c071 100644 --- a/src/Extensions/OdorControl.bonsai +++ b/src/Extensions/OdorControl.bonsai @@ -165,7 +165,7 @@ - olfactometerChannelsCount + olfactometesCount @@ -193,7 +193,7 @@ Item2 - olfactometerChannelsCount + olfactometesCount @@ -256,7 +256,7 @@ 3 + 4*it.Count() - olfactometerChannelsCount + olfactometesCount SetOdorMix diff --git a/src/Extensions/UserInterface.bonsai b/src/Extensions/UserInterface.bonsai index e9e2558..4160439 100644 --- a/src/Extensions/UserInterface.bonsai +++ b/src/Extensions/UserInterface.bonsai @@ -64,11 +64,7 @@ thisOdor - OdorIndex - - - GetOlfactometerIndex - it < 3 ? 0 : ((it - 3) / 4 + 1) + Item1 thisOlfactometerIndex @@ -77,7 +73,7 @@ thisOdor - OdorConfiguration.ChannelIndex + Item2 thisChannelIndex @@ -90,6 +86,9 @@ thisChannelIndex + + ChannelIndex + @@ -159,10 +158,10 @@ - + - - + + @@ -405,8 +404,8 @@ CurrentOdorBeingCalibrated - CalibratingOdor {0} ({1}) - OdorIndex,OdorConfiguration + Olfactometer {0} , OdorConfig {1} + Item1, Item2 diff --git a/src/aind_behavior_device_olfactometer/rig.py b/src/aind_behavior_device_olfactometer/rig.py index 730937a..3f1716c 100644 --- a/src/aind_behavior_device_olfactometer/rig.py +++ b/src/aind_behavior_device_olfactometer/rig.py @@ -3,7 +3,7 @@ import aind_behavior_services.rig.aind_manipulator as man from aind_behavior_services import rig from aind_behavior_services.rig import harp -from aind_behavior_services.rig import olfactometer as olf +from aind_behavior_services.rig import olfactometer as oc from pydantic import Field from . import __semver__ @@ -18,8 +18,8 @@ class AlicatFlowmeter(rig.Device): class OlfactometerCalibrationRig(rig.Rig): version: Literal[__semver__] = __semver__ - harp_olfactometer: olf.Olfactometer = Field(title="Olfactometer device") - harp_olfactometer_extension: list[olf.Olfactometer] = Field( + harp_olfactometer: oc.Olfactometer = Field(title="Olfactometer device") + harp_olfactometer_extension: list[oc.Olfactometer] = Field( default_factory=list, description="A collection of subordinate olfactometers that can be added to increase the number of independently delivered odors. The order of the list determines the order by which odors are numbered", ) diff --git a/src/aind_behavior_device_olfactometer/task_logic.py b/src/aind_behavior_device_olfactometer/task_logic.py index 569af6b..0ea9da0 100644 --- a/src/aind_behavior_device_olfactometer/task_logic.py +++ b/src/aind_behavior_device_olfactometer/task_logic.py @@ -1,23 +1,12 @@ -from typing import List, Literal +from typing import Literal -from aind_behavior_services.rig import olfactometer as olf from aind_behavior_services.task import Task, TaskParameters -from pydantic import BaseModel, Field +from pydantic import Field from . import __semver__ -class ChannelToCalibrate(BaseModel): - odor_index: int = Field( - title="Olfactometer odor channel index. This is an absolute count across all olfactometer channels" - ) - odor_configuration: olf.OlfactometerChannelConfig = Field(title="Olfactometer channel configuration") - - class OlfactometerCalibrationParameters(TaskParameters): - channel_config: List[ChannelToCalibrate] = Field( - default_factory=list, description="List of olfactometer channels to calibrate with their configurations" - ) full_flow_rate: float = Field(default=1000, ge=0, le=1000, description="Full flow rate of the olfactometer") n_repeats_per_stimulus: int = Field(default=1, ge=1, description="Number of repeats per stimulus") time_on: float = Field(default=1, ge=0, description="Time (s) the valve is open during calibration") diff --git a/src/main.bonsai b/src/main.bonsai index dcd113a..e574971 100644 --- a/src/main.bonsai +++ b/src/main.bonsai @@ -3,8 +3,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rx="clr-namespace:Bonsai.Reactive;assembly=Bonsai.Core" xmlns:p1="clr-namespace:AllenNeuralDynamics.VersionControl;assembly=AllenNeuralDynamics.VersionControl" - xmlns:p2="clr-namespace:AllenNeuralDynamics.AindBehaviorServices.DataTypes;assembly=AllenNeuralDynamics.AindBehaviorServices" - xmlns:p3="clr-namespace:AindBehaviorDeviceOlfactometerDataSchema;assembly=Extensions" + xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:p2="clr-namespace:AindBehaviorDeviceOlfactometerDataSchema;assembly=Extensions" + xmlns:p3="clr-namespace:AllenNeuralDynamics.AindBehaviorServices.DataTypes;assembly=AllenNeuralDynamics.AindBehaviorServices" xmlns:p4="clr-namespace:System.Reactive;assembly=System.Reactive.Core" xmlns:p5="clr-namespace:;assembly=Extensions" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -84,12 +85,12 @@ Repository - - SoftwareEvent - - + CurrentOdorBeingCalibrated + + SoftwareEvent + @@ -173,17 +174,34 @@ 0 + + + 0 + Odor + _100 + 100 + + + + + + + + + 1 + + - olfactometerChannelsCount + olfactometesCount - + - 3 + 12 0 @@ -213,15 +231,18 @@ - - - - + + + + - + + + + diff --git a/uv.lock b/uv.lock index 2699c01..e5f8f2d 100644 --- a/uv.lock +++ b/uv.lock @@ -124,7 +124,7 @@ wheels = [ [[package]] name = "aind-clabe" -version = "0.10.2" +version = "0.10.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aind-behavior-services" }, @@ -136,9 +136,9 @@ dependencies = [ { name = "rich" }, { name = "semver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/d3/4ec628e759b31bae8470f1b2f69cc6b1793d2e96c40a80b9fd8402132390/aind_clabe-0.10.2.tar.gz", hash = "sha256:13062f37c440382e81390ea26330842e671630dcb9bd5a590772b0293041b554", size = 69654, upload-time = "2026-04-17T22:41:36.864Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/8a/ad2231560f06aa416b6e1f0fd0ea20aa25c479dc63fbb3a5e126389b5a1c/aind_clabe-0.10.5.tar.gz", hash = "sha256:106ebbb48a76e82489df7e532bbf711947c2fc2cba5057362a0562962cef1ef9", size = 70746, upload-time = "2026-04-25T00:30:34.717Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/41/2555fac0bd26b3179e609cb50d39db08fce9538b051c35ac6ae4a4fcb6f2/aind_clabe-0.10.2-py3-none-any.whl", hash = "sha256:a6f04b3577e5d73e21c4c34538c20271815dc3a5496888d8c8219ee2f69128c2", size = 92819, upload-time = "2026-04-17T22:41:35.431Z" }, + { url = "https://files.pythonhosted.org/packages/cb/5a/38aed78e0fa7737f9e922fc477183aefc0175928aeff6086376d70dcd7a4/aind_clabe-0.10.5-py3-none-any.whl", hash = "sha256:efdf1497d18db48872cbfbb0a2a40b63f2baed9aecc8d57b9c85bf176f356eac", size = 94246, upload-time = "2026-04-25T00:30:33.663Z" }, ] [package.optional-dependencies] @@ -146,6 +146,7 @@ aind-services = [ { name = "aind-data-schema" }, { name = "aind-data-transfer-service" }, { name = "aind-watchdog-service" }, + { name = "msal", marker = "sys_platform == 'win32'" }, { name = "pykeepass" }, { name = "pyyaml" }, { name = "requests" }, @@ -720,6 +721,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b4/9a/94fa67d2aecf7703a68be18b97b8d1af09ec9c26c73b2c21d5e881e46353/contraqctor-0.5.3-py3-none-any.whl", hash = "sha256:9306521efd6165007409319be39d4a8d9e286028693237584dbfc9f87e974292", size = 67638, upload-time = "2025-11-10T17:31:18.482Z" }, ] +[[package]] +name = "cryptography" +version = "47.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/b2/7ffa7fe8207a8c42147ffe70c3e360b228160c1d85dc3faff16aaa3244c0/cryptography-47.0.0.tar.gz", hash = "sha256:9f8e55fe4e63613a5e1cc5819030f27b97742d720203a087802ce4ce9ceb52bb", size = 830863, upload-time = "2026-04-24T19:54:57.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/ed/5f524db1fade9c013aa618e1c99c6ed05e8ffc9ceee6cda22fed22dda3f4/cryptography-47.0.0-cp311-abi3-win32.whl", hash = "sha256:7fda2f02c9015db3f42bb8a22324a454516ed10a8c29ca6ece6cdbb5efe2a203", size = 3258581, upload-time = "2026-04-24T19:53:31.058Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/1b901990b174786569029f67542b3edf72ac068b6c3c8683c17e6a2f5363/cryptography-47.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:f5c3296dab66202f1b18a91fa266be93d6aa0c2806ea3d67762c69f60adc71aa", size = 3775309, upload-time = "2026-04-24T19:53:33.054Z" }, + { url = "https://files.pythonhosted.org/packages/31/98/dc4ad376ac5f1a1a7d4a83f7b0c6f2bcad36b5d2d8f30aeb482d3a7d9582/cryptography-47.0.0-cp314-cp314t-win32.whl", hash = "sha256:6eebcaf0df1d21ce1f90605c9b432dd2c4f4ab665ac29a40d5e3fc68f51b5e63", size = 3237158, upload-time = "2026-04-24T19:54:02.606Z" }, + { url = "https://files.pythonhosted.org/packages/bc/da/97f62d18306b5133468bc3f8cc73a3111e8cdc8cf8d3e69474d6e5fd2d1b/cryptography-47.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:51c9313e90bd1690ec5a75ed047c27c0b8e6c570029712943d6116ef9a90620b", size = 3758706, upload-time = "2026-04-24T19:54:04.433Z" }, + { url = "https://files.pythonhosted.org/packages/06/bd/0a9d3edbf5eadbac926d7b9b3cd0c4be584eeeae4a003d24d9eda4affbbd/cryptography-47.0.0-cp38-abi3-win32.whl", hash = "sha256:ed67ea4e0cfb5faa5bc7ecb6e2b8838f3807a03758eec239d6c21c8769355310", size = 3248487, upload-time = "2026-04-24T19:54:33.494Z" }, + { url = "https://files.pythonhosted.org/packages/60/80/5681af756d0da3a599b7bdb586fac5a1540f1bcefd2717a20e611ddade45/cryptography-47.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:835d2d7f47cdc53b3224e90810fb1d36ca94ea29cc1801fb4c1bc43876735769", size = 3755737, upload-time = "2026-04-24T19:54:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9c/51f28c3550276bcf35660703ba0ab829a90b88be8cd98a71ef23c2413913/cryptography-47.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cffbba3392df0fa8629bb7f43454ee2925059ee158e23c54620b9063912b86c8", size = 3698916, upload-time = "2026-04-24T19:54:49.782Z" }, +] + [[package]] name = "cycler" version = "0.12.1" @@ -1815,6 +1834,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, ] +[[package]] +name = "msal" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography", marker = "sys_platform == 'win32'" }, + { name = "pyjwt", extra = ["crypto"], marker = "sys_platform == 'win32'" }, + { name = "requests", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/cb/b02b0f748ac668922364ccb3c3bff5b71628a05f5adfec2ba2a5c3031483/msal-1.36.0.tar.gz", hash = "sha256:3f6a4af2b036b476a4215111c4297b4e6e236ed186cd804faefba23e4990978b", size = 174217, upload-time = "2026-04-09T10:20:33.525Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/d3/414d1f0a5f6f4fe5313c2b002c54e78a3332970feb3f5fed14237aa17064/msal-1.36.0-py3-none-any.whl", hash = "sha256:36ecac30e2ff4322d956029aabce3c82301c29f0acb1ad89b94edcabb0e58ec4", size = 121547, upload-time = "2026-04-09T10:20:32.336Z" }, +] + [[package]] name = "nbclient" version = "0.10.4" @@ -2450,6 +2483,20 @@ version = "1.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/66/ca/823d5c74a73d6b8b08e1f5aea12468ef334f0732c65cbb18df2a7f285c87/pygraphviz-1.14.tar.gz", hash = "sha256:c10df02377f4e39b00ae17c862f4ee7e5767317f1c6b2dfd04cea6acc7fc2bea", size = 106003, upload-time = "2024-09-29T18:31:12.471Z" } +[[package]] +name = "pyjwt" +version = "2.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography", marker = "sys_platform == 'win32'" }, +] + [[package]] name = "pykeepass" version = "4.1.1.post1" From 2b7317dbf89c1b67bb7ec696cab8975e0a122500 Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Fri, 24 Apr 2026 19:45:38 -0700 Subject: [PATCH 07/10] Update example against refactored definition --- examples/olfactometer.py | 34 ++++++++++------------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/examples/olfactometer.py b/examples/olfactometer.py index cb15d20..9d6ca5f 100644 --- a/examples/olfactometer.py +++ b/examples/olfactometer.py @@ -9,31 +9,8 @@ from aind_behavior_device_olfactometer import rig, task_logic from aind_behavior_device_olfactometer.rig import AlicatFlowmeter -channels2calibrate = [ - task_logic.ChannelToCalibrate( - odor_index=0, - odor_configuration=olf.OlfactometerChannelConfig( - channel_index=olf.OlfactometerChannel.Channel0, - channel_type=olf.OlfactometerChannelType.ODOR, - flow_rate=100, - odorant="Banana", - odorant_dilution=0.1, - ), - ), - task_logic.ChannelToCalibrate( - odor_index=3, - odor_configuration=olf.OlfactometerChannelConfig( - channel_index=olf.OlfactometerChannel.Channel3, - channel_type=olf.OlfactometerChannelType.CARRIER, - odorant="Air", - ), - ), -] - - calibration_logic = task_logic.OlfactometerCalibrationLogic( task_parameters=task_logic.OlfactometerCalibrationParameters( - channel_config=channels2calibrate, full_flow_rate=1000, n_repeats_per_stimulus=10, time_on=2, @@ -93,12 +70,21 @@ } ) +extra_olf = olf_calibration.model_copy() +extra_olf.channel_config[olf.OlfactometerChannel.Channel3] = olf.OlfactometerChannelConfig( + channel_index=olf.OlfactometerChannel.Channel3, + channel_type=olf.OlfactometerChannelType.ODOR, + flow_rate=100, + odorant="Vanilla", +) + _rig = rig.OlfactometerCalibrationRig( computer_name="TestPC", - data_directory="c:/data", + data_directory=r"C:/data", rig_name="OlfactometerRig", harp_olfactometer=olf.Olfactometer(port_name="COM10", calibration=olf_calibration), + harp_olfactometer_extension=[olf.Olfactometer(port_name="COM11", calibration=extra_olf)], harp_analog_input=HarpAnalogInput(port_name="COM8"), harp_clock_generator=HarpWhiteRabbit(port_name="COM9"), harp_manipulator=man.AindManipulator(port_name="COM7", calibration=manipulator_calibration), From 40c2d360b84f09d8e1ea3d491b6f2fc7b3b54d38 Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:35:46 -0700 Subject: [PATCH 08/10] Fix the number of channels assignment --- src/Extensions/CreateHubOdorMixture.cs | 2 +- .../CreateOneHotConcentrationArray.cs | 2 +- src/Extensions/OdorControl.bonsai | 14 +++++++---- src/Extensions/UserInterface.bonsai | 24 ++++++++++++++----- src/main.bonsai | 8 +++---- 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/Extensions/CreateHubOdorMixture.cs b/src/Extensions/CreateHubOdorMixture.cs index 4e61a97..e45f88d 100644 --- a/src/Extensions/CreateHubOdorMixture.cs +++ b/src/Extensions/CreateHubOdorMixture.cs @@ -43,7 +43,7 @@ public int OlfactometerChannelCount private IList ConstructMessage(IList channelConcentrations) { - int nChannels = OlfactometerChannelCount; + int nChannels = 3 + (4 * (OlfactometerChannelCount - 1)); if (channelConcentrations.Count > nChannels) { throw new ArgumentException("The number of channel concentrations provided " + channelConcentrations.Count + " does not match the expected number based on the olfactometer count " + nChannels + "."); diff --git a/src/Extensions/CreateOneHotConcentrationArray.cs b/src/Extensions/CreateOneHotConcentrationArray.cs index 30d1401..954d174 100644 --- a/src/Extensions/CreateOneHotConcentrationArray.cs +++ b/src/Extensions/CreateOneHotConcentrationArray.cs @@ -33,7 +33,7 @@ public IObservable> Process(IObservable(new double[numberOfChannels]); - var globalChannelIndex = olfactometerIndex == 0 ? channelConfig.ChannelIndex : (3 + olfactometerIndex * 4) + channelConfig.ChannelIndex; + var globalChannelIndex = olfactometerIndex == 0 ? channelConfig.ChannelIndex : (3 + (olfactometerIndex-1) * 4) + channelConfig.ChannelIndex; if (globalChannelIndex >= oneHotArray.Count) { throw new ArgumentOutOfRangeException("Calculated global channel index " + globalChannelIndex + " exceeds the one-hot array size."); diff --git a/src/Extensions/OdorControl.bonsai b/src/Extensions/OdorControl.bonsai index 970c071..12a3010 100644 --- a/src/Extensions/OdorControl.bonsai +++ b/src/Extensions/OdorControl.bonsai @@ -5,8 +5,8 @@ xmlns:p1="clr-namespace:Harp.Olfactometer;assembly=Harp.Olfactometer" xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:p2="clr-namespace:;assembly=Extensions" - xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" + xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" xmlns:gl="clr-namespace:Bonsai.Shaders;assembly=Bonsai.Shaders" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -252,8 +252,13 @@ HarpOlfactometerExtension - - 3 + 4*it.Count() + + Count + + + + 1 + olfactometesCount @@ -519,9 +524,10 @@ - + + \ No newline at end of file diff --git a/src/Extensions/UserInterface.bonsai b/src/Extensions/UserInterface.bonsai index 4160439..aab3b69 100644 --- a/src/Extensions/UserInterface.bonsai +++ b/src/Extensions/UserInterface.bonsai @@ -93,6 +93,11 @@ + + + 1 + + ActualFlowAddress @@ -145,6 +150,12 @@ + + Include + + + + TimestampedFloat @@ -165,14 +176,16 @@ - + - - - - + + + + + + @@ -416,7 +429,6 @@ false true Microsoft Sans Serif, 16pt - Start! diff --git a/src/main.bonsai b/src/main.bonsai index e574971..a0babc4 100644 --- a/src/main.bonsai +++ b/src/main.bonsai @@ -12,14 +12,14 @@ - + - C:\git\AllenNeuralDynamics\Aind.Behavior.Device.Olfactometer\local\olfactometer_calibration_logic.json - C:\git\AllenNeuralDynamics\Aind.Behavior.Device.Olfactometer\local\olfactometer_rig.json - C:\git\AllenNeuralDynamics\Aind.Behavior.Device.Olfactometer\local\olfactometer_session.json + + + From 35ed7f95c167895762b870f6dd56917ec0044e24 Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Tue, 28 Apr 2026 08:42:09 -0700 Subject: [PATCH 09/10] Align names with VrForaging Co-authored-by: Copilot --- src/Extensions/CalibrationLogic.bonsai | 32 +++++++++---------- src/Extensions/CreateHubOdorMixture.cs | 2 +- .../CreateOneHotConcentrationArray.cs | 10 +++--- src/Extensions/OdorControl.bonsai | 21 +++++------- src/main.bonsai | 6 ++-- 5 files changed, 33 insertions(+), 38 deletions(-) diff --git a/src/Extensions/CalibrationLogic.bonsai b/src/Extensions/CalibrationLogic.bonsai index 9e671ec..f0ddf03 100644 --- a/src/Extensions/CalibrationLogic.bonsai +++ b/src/Extensions/CalibrationLogic.bonsai @@ -120,16 +120,16 @@ thisOdor - olfactometesCount + olfactometerChannelCount - + - 12 + 12 1 @@ -145,7 +145,7 @@ - olfactometesCount + olfactometerChannelCount @@ -377,16 +377,16 @@ thisOdor - olfactometesCount + olfactometerChannelCount - + - 12 + 12 0 @@ -402,7 +402,7 @@ - olfactometesCount + olfactometerChannelCount @@ -493,16 +493,16 @@ thisOdor - olfactometesCount + olfactometerChannelCount - + - 12 + 12 1 @@ -518,7 +518,7 @@ - olfactometesCount + olfactometerChannelCount @@ -597,16 +597,16 @@ thisOdor - olfactometesCount + olfactometerChannelCount - + - 12 + 12 0 @@ -622,7 +622,7 @@ - olfactometesCount + olfactometerChannelCount diff --git a/src/Extensions/CreateHubOdorMixture.cs b/src/Extensions/CreateHubOdorMixture.cs index e45f88d..4e61a97 100644 --- a/src/Extensions/CreateHubOdorMixture.cs +++ b/src/Extensions/CreateHubOdorMixture.cs @@ -43,7 +43,7 @@ public int OlfactometerChannelCount private IList ConstructMessage(IList channelConcentrations) { - int nChannels = 3 + (4 * (OlfactometerChannelCount - 1)); + int nChannels = OlfactometerChannelCount; if (channelConcentrations.Count > nChannels) { throw new ArgumentException("The number of channel concentrations provided " + channelConcentrations.Count + " does not match the expected number based on the olfactometer count " + nChannels + "."); diff --git a/src/Extensions/CreateOneHotConcentrationArray.cs b/src/Extensions/CreateOneHotConcentrationArray.cs index 954d174..681c161 100644 --- a/src/Extensions/CreateOneHotConcentrationArray.cs +++ b/src/Extensions/CreateOneHotConcentrationArray.cs @@ -11,11 +11,11 @@ [WorkflowElementCategory(ElementCategory.Transform)] public class CreateOneHotConcentrationArray { - private int olfactometerCount = 12; - public int OlfactometerCount + private int olfactometerChannelsCount = 12; + public int OlfactometerChannelsCount { - get { return olfactometerCount; } - set { olfactometerCount = value; } + get { return olfactometerChannelsCount; } + set { olfactometerChannelsCount = value; } } private double concentration = 1.0; @@ -31,7 +31,7 @@ public IObservable> Process(IObservable(new double[numberOfChannels]); var globalChannelIndex = olfactometerIndex == 0 ? channelConfig.ChannelIndex : (3 + (olfactometerIndex-1) * 4) + channelConfig.ChannelIndex; if (globalChannelIndex >= oneHotArray.Count) diff --git a/src/Extensions/OdorControl.bonsai b/src/Extensions/OdorControl.bonsai index 12a3010..2e6fb3d 100644 --- a/src/Extensions/OdorControl.bonsai +++ b/src/Extensions/OdorControl.bonsai @@ -5,8 +5,8 @@ xmlns:p1="clr-namespace:Harp.Olfactometer;assembly=Harp.Olfactometer" xmlns:harp="clr-namespace:Bonsai.Harp;assembly=Bonsai.Harp" xmlns:p2="clr-namespace:;assembly=Extensions" - xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:scr="clr-namespace:Bonsai.Scripting.Expressions;assembly=Bonsai.Scripting.Expressions" + xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:gl="clr-namespace:Bonsai.Shaders;assembly=Bonsai.Shaders" xmlns="https://bonsai-rx.org/2018/workflow"> @@ -165,7 +165,7 @@ - olfactometesCount + olfactometerChannelCount @@ -193,7 +193,7 @@ Item2 - olfactometesCount + olfactometerChannelCount @@ -252,16 +252,12 @@ HarpOlfactometerExtension - - Count - - - - 1 - + + CountChannels + 3 + (it.Count * 4) - olfactometesCount + olfactometerChannelCount SetOdorMix @@ -524,10 +520,9 @@ - + - \ No newline at end of file diff --git a/src/main.bonsai b/src/main.bonsai index a0babc4..e26632c 100644 --- a/src/main.bonsai +++ b/src/main.bonsai @@ -192,16 +192,16 @@ - olfactometesCount + olfactometerChannelCount - + - 12 + 12 0 From 781b1f4627df9d1a8c09e70ce601a228fea0531e Mon Sep 17 00:00:00 2001 From: bruno-f-cruz <7049351+bruno-f-cruz@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:45:50 -0700 Subject: [PATCH 10/10] Set the number of olfactometer channels --- src/main.bonsai | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main.bonsai b/src/main.bonsai index e26632c..cc35fbf 100644 --- a/src/main.bonsai +++ b/src/main.bonsai @@ -205,6 +205,14 @@ 0 + + olfactometerChannelCount + + + + + + 100 @@ -237,12 +245,14 @@ - + - + + +