From a0a9fa7aea5864f4ea3ce20b1501a7387859e511 Mon Sep 17 00:00:00 2001 From: Nathan Allen Date: Sat, 7 Mar 2026 09:22:28 -0500 Subject: [PATCH 1/6] feat: implement naming engine and infector --- .braids.json | 6 + poetry.lock | 146 +++++- pyproject.toml | 1 + src/ocp_agent/dictionaries/abbrs.json | 189 ++++++++ src/ocp_agent/parsers/inflector.py | 255 ++++++++++ src/ocp_agent/parsers/openapi_parser.py | 105 +---- src/ocp_agent/parsers/tool_naming.py | 468 +++++++++++++++++++ tests/fixtures/test_spec.json | 1 - tests/fixtures/test_spec.yaml | 1 - tests/test_inflector.py | 349 ++++++++++++++ tests/test_openapi_parser.py | 32 -- tests/test_schema_discovery.py | 131 +----- tests/test_tool_naming.py | 597 ++++++++++++++++++++++++ 13 files changed, 2032 insertions(+), 249 deletions(-) create mode 100644 src/ocp_agent/dictionaries/abbrs.json create mode 100644 src/ocp_agent/parsers/inflector.py create mode 100644 src/ocp_agent/parsers/tool_naming.py create mode 100644 tests/test_inflector.py create mode 100644 tests/test_tool_naming.py diff --git a/.braids.json b/.braids.json index cda2b45..5fbfac9 100644 --- a/.braids.json +++ b/.braids.json @@ -6,6 +6,12 @@ "branch": "main", "path": "schemas", "revision": "b1ba770eb3e1bd245928088f1e7be0516d0fe820" + }, + "src/ocp_agent/dictionaries": { + "url": "https://github.com/opencontextprotocol/ocp-registry.git", + "branch": "main", + "path": "data/dictionaries", + "revision": "HEAD" } } } diff --git a/poetry.lock b/poetry.lock index 4cf7cd5..38b2e65 100644 --- a/poetry.lock +++ b/poetry.lock @@ -192,6 +192,128 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.13.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "coverage-7.13.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0fc31c787a84f8cd6027eba44010517020e0d18487064cd3d8968941856d1415"}, + {file = "coverage-7.13.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a32ebc02a1805adf637fc8dec324b5cdacd2e493515424f70ee33799573d661b"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e24f9156097ff9dc286f2f913df3a7f63c0e333dcafa3c196f2c18b4175ca09a"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8041b6c5bfdc03257666e9881d33b1abc88daccaf73f7b6340fb7946655cd10f"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a09cfa6a5862bc2fc6ca7c3def5b2926194a56b8ab78ffcf617d28911123012"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:296f8b0af861d3970c2a4d8c91d48eb4dd4771bcef9baedec6a9b515d7de3def"}, + {file = "coverage-7.13.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e101609bcbbfb04605ea1027b10dc3735c094d12d40826a60f897b98b1c30256"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aa3feb8db2e87ff5e6d00d7e1480ae241876286691265657b500886c98f38bda"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4fc7fa81bbaf5a02801b65346c8b3e657f1d93763e58c0abdf7c992addd81a92"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:33901f604424145c6e9c2398684b92e176c0b12df77d52db81c20abd48c3794c"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:bb28c0f2cf2782508a40cec377935829d5fcc3ad9a3681375af4e84eb34b6b58"}, + {file = "coverage-7.13.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9d107aff57a83222ddbd8d9ee705ede2af2cc926608b57abed8ef96b50b7e8f9"}, + {file = "coverage-7.13.4-cp310-cp310-win32.whl", hash = "sha256:a6f94a7d00eb18f1b6d403c91a88fd58cfc92d4b16080dfdb774afc8294469bf"}, + {file = "coverage-7.13.4-cp310-cp310-win_amd64.whl", hash = "sha256:2cb0f1e000ebc419632bbe04366a8990b6e32c4e0b51543a6484ffe15eaeda95"}, + {file = "coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053"}, + {file = "coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef"}, + {file = "coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6"}, + {file = "coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9"}, + {file = "coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9"}, + {file = "coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f"}, + {file = "coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f"}, + {file = "coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459"}, + {file = "coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3"}, + {file = "coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985"}, + {file = "coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0"}, + {file = "coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246"}, + {file = "coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126"}, + {file = "coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d"}, + {file = "coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9"}, + {file = "coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242"}, + {file = "coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea"}, + {file = "coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a"}, + {file = "coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d"}, + {file = "coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd"}, + {file = "coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af"}, + {file = "coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d"}, + {file = "coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9"}, + {file = "coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0"}, + {file = "coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b"}, + {file = "coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9"}, + {file = "coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd"}, + {file = "coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997"}, + {file = "coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601"}, + {file = "coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a"}, + {file = "coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5"}, + {file = "coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0"}, + {file = "coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb"}, + {file = "coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505"}, + {file = "coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2"}, + {file = "coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056"}, + {file = "coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72"}, + {file = "coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39"}, + {file = "coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0"}, + {file = "coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea"}, + {file = "coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932"}, + {file = "coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b"}, + {file = "coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0"}, + {file = "coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -557,6 +679,26 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "7.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861"}, + {file = "pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1"}, +] + +[package.dependencies] +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "pyyaml" version = "6.0.3" @@ -829,7 +971,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version == \"3.10\"" +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867"}, {file = "tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9"}, @@ -929,4 +1071,4 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "5989fdfe860d50bd6d895dfc797208b80976779ec7524487159fbaede09c450b" +content-hash = "12fb023b41a3fd5096a632acb6952aa2c1be914032de9d305727a9a20d1b6d59" diff --git a/pyproject.toml b/pyproject.toml index 2487e1d..658634f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,7 @@ pydantic = "^2.12.0" [tool.poetry.group.dev.dependencies] pytest = "^9.0" requests-mock = "^1.12.1" +pytest-cov = "^7.0.0" [build-system] requires = ["poetry-core"] diff --git a/src/ocp_agent/dictionaries/abbrs.json b/src/ocp_agent/dictionaries/abbrs.json new file mode 100644 index 0000000..6e871f2 --- /dev/null +++ b/src/ocp_agent/dictionaries/abbrs.json @@ -0,0 +1,189 @@ +{ + "version": "0.1.0", + "abbreviations": [ + { + "word": "repository", + "abbr": "repo" + }, + { + "word": "repositories", + "abbr": "repos" + }, + { + "word": "configuration", + "abbr": "config" + }, + { + "word": "application", + "abbr": "app" + }, + { + "word": "applications", + "abbr": "apps" + }, + { + "word": "authentication", + "abbr": "auth" + }, + { + "word": "authorization", + "abbr": "authz" + }, + { + "word": "administrator", + "abbr": "admin" + }, + { + "word": "administrators", + "abbr": "admins" + }, + { + "word": "environment", + "abbr": "env" + }, + { + "word": "environments", + "abbr": "envs" + }, + { + "word": "organization", + "abbr": "org" + }, + { + "word": "organizations", + "abbr": "orgs" + }, + { + "word": "information", + "abbr": "info" + }, + { + "word": "description", + "abbr": "desc" + }, + { + "word": "specification", + "abbr": "spec" + }, + { + "word": "specifications", + "abbr": "specs" + }, + { + "word": "parameter", + "abbr": "param" + }, + { + "word": "parameters", + "abbr": "params" + }, + { + "word": "argument", + "abbr": "arg" + }, + { + "word": "arguments", + "abbr": "args" + }, + { + "word": "reference", + "abbr": "ref" + }, + { + "word": "references", + "abbr": "refs" + }, + { + "word": "attribute", + "abbr": "attr" + }, + { + "word": "attributes", + "abbr": "attrs" + }, + { + "word": "statistic", + "abbr": "stat" + }, + { + "word": "statistics", + "abbr": "stats" + }, + { + "word": "document", + "abbr": "doc" + }, + { + "word": "documents", + "abbr": "docs" + }, + { + "word": "message", + "abbr": "msg" + }, + { + "word": "messages", + "abbr": "msgs" + }, + { + "word": "database", + "abbr": "db" + }, + { + "word": "databases", + "abbr": "dbs" + }, + { + "word": "temporary", + "abbr": "temp" + }, + { + "word": "maximum", + "abbr": "max" + }, + { + "word": "minimum", + "abbr": "min" + }, + { + "word": "kubernetes", + "abbr": "k8s" + }, + { + "word": "internationalization", + "abbr": "i18n" + }, + { + "word": "localization", + "abbr": "l10n" + }, + { + "word": "metadata", + "abbr": "meta" + }, + { + "word": "identifier", + "abbr": "id" + }, + { + "word": "identifiers", + "abbr": "ids" + }, + { + "word": "variable", + "abbr": "var" + }, + { + "word": "variables", + "abbr": "vars" + }, + { + "word": "package", + "abbr": "pkg" + }, + { + "word": "packages", + "abbr": "pkgs" + } + ] +} diff --git a/src/ocp_agent/parsers/inflector.py b/src/ocp_agent/parsers/inflector.py new file mode 100644 index 0000000..24463ff --- /dev/null +++ b/src/ocp_agent/parsers/inflector.py @@ -0,0 +1,255 @@ +""" +English pluralization and singularization rules. + +Based on: https://github.com/weixu365/pluralizer-py +Adapted for API path resource name inflection. +""" + +import re +from typing import List, Tuple, Pattern, Union + +# Irregular word mappings (singular → plural) +IRREGULARS = [ + ("person", "people"), + ("man", "men"), + ("human", "humans"), + ("child", "children"), + ("sex", "sexes"), + ("move", "moves"), + ("cow", "kine"), + ("zombie", "zombies"), + ("goose", "geese"), + ("foot", "feet"), + ("tooth", "teeth"), + ("quiz", "quizzes"), + ("ox", "oxen"), + ("axe", "axes"), + ("die", "dice"), + ("yes", "yeses"), + ("mouse", "mice"), + ("datum", "data"), + ("schema", "schemata"), +] + +# Build reverse mapping for singularization +IRREGULAR_SINGULAR_MAP = {s: p for s, p in IRREGULARS} +IRREGULAR_PLURAL_MAP = {p: s for s, p in IRREGULARS} + +# Uncountable words (never change) +UNCOUNTABLES = [ + "equipment", + "information", + "rice", + "money", + "species", + "series", + "fish", + "sheep", + "moose", + "deer", + "news", + "audio", + "video", + "hardware", + "software", + "firmware", + "metadata", + "data", + "analytics", + "media", + "traffic", + "mail", + "police", + "cattle", + "staff", + "personnel", + "research", + "music", + "garbage", + "chaos", + "evidence", + "furniture", + "luggage", + "machinery", + "wildlife", + "health", + "wealth", + "history", +] + +# Pluralization rules (pattern, replacement) +PLURALIZATION_RULES: List[Tuple[Union[str, Pattern], str]] = [ + (re.compile(r"(?i)(quiz)$"), r"\1zes"), + (re.compile(r"(?i)^(oxen)$"), r"\1"), + (re.compile(r"(?i)^(ox)$"), r"\1en"), + (re.compile(r"(?i)(m|l)ice$"), r"\1ice"), + (re.compile(r"(?i)(m|l)ouse$"), r"\1ice"), + (re.compile(r"(?i)(matr|vert|ind)(?:ix|ex)$"), r"\1ices"), + (re.compile(r"(?i)(x|ch|ss|sh)$"), r"\1es"), + (re.compile(r"(?i)([^aeiouy]|qu)y$"), r"\1ies"), + (re.compile(r"(?i)(hive)$"), r"\1s"), + (re.compile(r"(?i)(?:([^f])fe|([lr])f)$"), r"\1\2ves"), + (re.compile(r"(?i)sis$"), r"ses"), + (re.compile(r"(?i)([ti])a$"), r"\1a"), + (re.compile(r"(?i)([ti])um$"), r"\1a"), + (re.compile(r"(?i)(buffal|tomat|potat)o$"), r"\1oes"), + (re.compile(r"(?i)(bu)s$"), r"\1ses"), + (re.compile(r"(?i)(alias|status)$"), r"\1es"), + (re.compile(r"(?i)(octop|vir)us$"), r"\1i"), + (re.compile(r"(?i)(ax|test)is$"), r"\1es"), + (re.compile(r"(?i)s$"), r"s"), + (re.compile(r"$"), r"s"), +] + +# Singularization rules (pattern, replacement) +SINGULARIZATION_RULES: List[Tuple[Union[str, Pattern], str]] = [ + (re.compile(r"(?i)(quiz)zes$"), r"\1"), + (re.compile(r"(?i)(matr)ices$"), r"\1ix"), + (re.compile(r"(?i)(vert|ind)ices$"), r"\1ex"), + (re.compile(r"(?i)^(ox)en"), r"\1"), + (re.compile(r"(?i)(alias|status)es$"), r"\1"), + (re.compile(r"(?i)(octop|vir)i$"), r"\1us"), + (re.compile(r"(?i)(cris|ax|test)es$"), r"\1is"), + (re.compile(r"(?i)(shoe)s$"), r"\1"), + (re.compile(r"(?i)(o)es$"), r"\1"), + (re.compile(r"(?i)(bus)es$"), r"\1"), + (re.compile(r"(?i)([m|l])ice$"), r"\1ouse"), + (re.compile(r"(?i)(x|ch|ss|sh)es$"), r"\1"), + (re.compile(r"(?i)(m)ovies$"), r"\1ovie"), + (re.compile(r"(?i)(s)eries$"), r"\1eries"), + (re.compile(r"(?i)([^aeiouy]|qu)ies$"), r"\1y"), + (re.compile(r"(?i)([lr])ves$"), r"\1f"), + (re.compile(r"(?i)(tive)s$"), r"\1"), + (re.compile(r"(?i)(hive)s$"), r"\1"), + (re.compile(r"(?i)([^f])ves$"), r"\1fe"), + (re.compile(r"(?i)(^analy)ses$"), r"\1sis"), + (re.compile(r"(?i)((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$"), r"\1\2sis"), + (re.compile(r"(?i)([ti])a$"), r"\1um"), + (re.compile(r"(?i)(n)ews$"), r"\1ews"), + (re.compile(r"(?i)s$"), r""), +] + + +def is_uncountable(word: str) -> bool: + """Check if word is uncountable (never pluralized/singularized).""" + return word.lower() in UNCOUNTABLES + + +def pluralize(word: str) -> str: + """ + Convert singular word to plural form. + + Args: + word: Singular word + + Returns: + Plural form of the word + + Examples: + repository → repositories + dashboard → dashboards + person → people + data → data (uncountable) + """ + if not word: + return word + + # Check uncountables first + if is_uncountable(word): + return word + + # Check irregular mappings + word_lower = word.lower() + if word_lower in IRREGULAR_SINGULAR_MAP: + plural = IRREGULAR_SINGULAR_MAP[word_lower] + # Preserve original capitalization + if word[0].isupper(): + return plural.capitalize() + return plural + + # Apply pluralization rules + for pattern, replacement in PLURALIZATION_RULES: + if isinstance(pattern, str): + if word.endswith(pattern): + return word[:-len(pattern)] + replacement + else: + match = pattern.search(word) + if match: + return pattern.sub(replacement, word) + + return word + + +def singularize(word: str) -> str: + """ + Convert plural word to singular form. + + Args: + word: Plural word + + Returns: + Singular form of the word + + Examples: + repositories → repository + dashboards → dashboard + people → person + data → data (uncountable) + """ + if not word: + return word + + # Check uncountables first + if is_uncountable(word): + return word + + # Check irregular mappings + word_lower = word.lower() + if word_lower in IRREGULAR_PLURAL_MAP: + singular = IRREGULAR_PLURAL_MAP[word_lower] + # Preserve original capitalization + if word[0].isupper(): + return singular.capitalize() + return singular + + # Apply singularization rules + for pattern, replacement in SINGULARIZATION_RULES: + if isinstance(pattern, str): + if word.endswith(pattern): + return word[:-len(pattern)] + replacement + else: + match = pattern.search(word) + if match: + return pattern.sub(replacement, word) + + return word + + +def is_plural(word: str) -> bool: + """ + Check if a word is likely in plural form. + + This is a heuristic check - singularize and compare. + """ + if not word or is_uncountable(word): + return False + + singular = singularize(word) + return singular != word + + +def is_singular(word: str) -> bool: + """ + Check if a word is likely in singular form. + + This is a heuristic check - pluralize and compare. + """ + if not word or is_uncountable(word): + return False + + # If it's a known irregular plural, it's not singular + if word.lower() in IRREGULAR_PLURAL_MAP: + return False + + plural = pluralize(word) + return plural != word diff --git a/src/ocp_agent/parsers/openapi_parser.py b/src/ocp_agent/parsers/openapi_parser.py index 8eddf7a..e40e870 100644 --- a/src/ocp_agent/parsers/openapi_parser.py +++ b/src/ocp_agent/parsers/openapi_parser.py @@ -9,6 +9,7 @@ from typing import Dict, List, Any, Optional from .base import APISpecParser, OCPAPISpec, OCPTool +from .tool_naming import ToolNamingEngine from ..errors import SchemaDiscoveryError logger = logging.getLogger(__name__) @@ -32,6 +33,7 @@ class OpenAPIParser(APISpecParser): def __init__(self): self._spec_version: Optional[str] = None + self.naming_engine = ToolNamingEngine() def can_parse(self, spec_data: Dict[str, Any]) -> bool: """Check if this is an OpenAPI or Swagger specification.""" @@ -64,7 +66,7 @@ def parse( self._spec_version = self._detect_spec_version(spec_data) # Parse the specification - api_spec = self._parse_openapi_spec(spec_data, base_url_override) + api_spec = self._parse_openapi_spec(spec_data, base_url_override, path_prefix) # Apply resource filtering if specified if include_resources: @@ -105,7 +107,12 @@ def _detect_spec_version(self, spec: Dict[str, Any]) -> str: raise SchemaDiscoveryError("Unable to detect spec version: missing 'swagger' or 'openapi' field") - def _parse_openapi_spec(self, spec_data: Dict[str, Any], base_url_override: Optional[str] = None) -> OCPAPISpec: + def _parse_openapi_spec( + self, + spec_data: Dict[str, Any], + base_url_override: Optional[str] = None, + path_prefix: Optional[str] = None + ) -> OCPAPISpec: """Parse OpenAPI specification into OCP tools""" # Extract basic info @@ -130,7 +137,7 @@ def _parse_openapi_spec(self, spec_data: Dict[str, Any], base_url_override: Opti for method, operation in path_item.items(): if method.lower() in SUPPORTED_HTTP_METHODS: tool = self._create_tool_from_operation( - path, method.upper(), operation, spec_data, memo_cache + path, method.upper(), operation, spec_data, memo_cache, path_prefix ) if tool: tools.append(tool) @@ -163,99 +170,27 @@ def _extract_base_url(self, spec_data: Dict[str, Any]) -> str: return servers[0].get('url', '') return '' - def _normalize_tool_name(self, name: str) -> str: - """Normalize tool name to camelCase, removing special characters. - - Converts various naming patterns to consistent camelCase: - - 'meta/root' → 'metaRoot' - - 'repos/disable-vulnerability-alerts' → 'reposDisableVulnerabilityAlerts' - - 'admin_apps_approve' → 'adminAppsApprove' - - 'FetchAccount' → 'fetchAccount' - - 'v2010/Accounts' → 'v2010Accounts' - - 'get_users_list' → 'getUsersList' - - 'SMS/send' → 'smsSend' - """ - # Handle empty or None names - if not name: - return name - - # First, split PascalCase/camelCase words (e.g., "FetchAccount" -> "Fetch Account") - # Insert space before uppercase letters that follow lowercase letters or digits - pascal_split = re.sub(r'([a-z0-9])([A-Z])', r'\1 \2', name) - - # Replace separators (/, _, -, .) with spaces for processing - # Also handle multiple consecutive separators like // - normalized = re.sub(r'[/_.-]+', ' ', pascal_split) - - # Split into words and filter out empty strings - words = [word for word in normalized.split() if word] - - if not words: - return name - - # Convert to camelCase: first word lowercase, rest capitalize - camel_case_words = [words[0].lower()] - for word in words[1:]: - camel_case_words.append(word.capitalize()) - - return ''.join(camel_case_words) - - def _is_valid_tool_name(self, name: str) -> bool: - """Check if a normalized tool name is valid. - - A valid tool name must: - - Not be empty - - Not consist only of special characters - - Start with a letter - - Contain at least one alphanumeric character - """ - if not name: - return False - - # Must start with a letter - if not name[0].isalpha(): - return False - - # Must contain at least one alphanumeric character - if not any(c.isalnum() for c in name): - return False - - return True - def _create_tool_from_operation( self, path: str, method: str, operation: Dict[str, Any], spec_data: Dict[str, Any], - memo_cache: Dict[str, Any] + memo_cache: Dict[str, Any], + path_prefix: Optional[str] = None ) -> Optional[OCPTool]: """Create OCP tool from OpenAPI operation""" - # Generate tool name with proper validation and fallback logic - operation_id = operation.get('operationId') - tool_name = None - - # Try operationId first - if operation_id: - normalized_name = self._normalize_tool_name(operation_id) - if self._is_valid_tool_name(normalized_name): - tool_name = normalized_name - - # If operationId failed, try fallback naming - if not tool_name: - # Generate name from path and method - clean_path = path.replace('/', '_').replace('{', '').replace('}', '') - fallback_name = f"{method.lower()}{clean_path}" - normalized_fallback = self._normalize_tool_name(fallback_name) - if self._is_valid_tool_name(normalized_fallback): - tool_name = normalized_fallback - - # If we can't generate a valid tool name, skip this operation - if not tool_name: - logger.warning(f"Skipping operation {method} {path}: unable to generate valid tool name") + # Generate tool name using naming engine + try: + tool_name = self.naming_engine.generate_tool_name(path, method, path_prefix) + except Exception as e: + logger.warning(f"Skipping operation {method} {path}: failed to generate tool name: {e}") return None + # Store operation ID for reference + operation_id = operation.get('operationId') + # Get description description = operation.get('summary', '') or operation.get('description', '') if not description: diff --git a/src/ocp_agent/parsers/tool_naming.py b/src/ocp_agent/parsers/tool_naming.py new file mode 100644 index 0000000..a7e7fe5 --- /dev/null +++ b/src/ocp_agent/parsers/tool_naming.py @@ -0,0 +1,468 @@ +""" +Tool naming engine for generating semantic, deterministic tool names from REST API paths. + +This module is format-agnostic and shared across all spec parsers (OpenAPI, Google Discovery, etc.). +It takes raw REST paths and generates consistent camelCase tool names. +""" + +import re +import json +from pathlib import Path +from typing import Dict, List, Optional +import logging + +from . import inflector + +logger = logging.getLogger(__name__) + +# Action verbs that are redundant with HTTP methods +# These are stripped from path segments (e.g., /calls.participants.add → /calls/participants) +ACTION_VERBS = { + "add", "remove", "create", "delete", "get", "list", "update", "set", + "post", "archive", "unarchive", "enable", "disable", "open", "close", + "invite", "kick", "join", "leave", "mark", "unmark", "end", "publish", + "upload", "share", "complete", "rename", "revoke", "fetch", "put", "patch" +} + + +class ToolNamingEngine: + """ + Generates tool names from REST API paths using a deterministic algorithm. + + The naming engine is responsible for: + - Stripping path prefixes (e.g., /v1, /rest/api/3, /api) + - Stripping file extensions (.json, .xml, .yaml) + - Parsing path into resource and parameter components + - Detecting container resources to skip + - Determining appropriate HTTP verb (list, get, create, update, delete) + - Applying singularization rules + - Applying abbreviations from dictionary + - Building camelCase names + """ + + def __init__(self, abbreviations: Optional[Dict[str, str]] = None): + """ + Initialize the naming engine. + + Args: + abbreviations: Dictionary mapping full words to abbreviations. + If None, loads from bundled abbreviations.json + """ + if abbreviations is None: + abbreviations = self._load_abbreviations() + self.abbreviations = abbreviations + + def _load_abbreviations(self) -> Dict[str, str]: + """Load abbreviations from bundled JSON file.""" + try: + abbr_path = Path(__file__).parent.parent / 'dictionaries' / 'abbrs.json' + with open(abbr_path, 'r') as f: + data = json.load(f) + # Convert array of objects to dict for fast lookup + return {item['word']: item['abbr'] for item in data['abbreviations']} + except Exception as e: + logger.warning(f"Failed to load abbreviations: {e}. Using empty dict.") + return {} + + def _clean_path(self, path: str, path_prefix: Optional[str] = None) -> str: + """ + Clean path by stripping prefix and file extensions. + + Args: + path: Raw API path + path_prefix: Optional prefix to strip (case-insensitive) + + Returns: + Cleaned path + + Examples: + ('/v1/balance', '/v1') → '/balance' + ('/rest/api/3/issue/{id}', '/rest/api/3') → '/issue/{id}' + ('/Accounts.json', None) → '/Accounts' + """ + cleaned = path + + # Strip path prefix if provided + if path_prefix: + prefix_lower = path_prefix.lower() + path_lower = path.lower() + if path_lower.startswith(prefix_lower): + cleaned = path[len(path_prefix):] + + # Strip file extensions + cleaned = re.sub(r'\.(json|xml|yaml|yml)$', '', cleaned) + + return cleaned + + def _parse_path_components(self, path: str) -> List[Dict[str, str]]: + """ + Parse path into structured components. + + Args: + path: Cleaned API path (prefix and extensions already stripped) + + Returns: + List of dicts with 'type' (RESOURCE or PARAM) and 'value' + + Examples: + /repos/{owner}/{repo}/codespaces/{id} → [ + {"type": "RESOURCE", "value": "repos"}, + {"type": "PARAM", "value": "owner"}, + {"type": "PARAM", "value": "repo"}, + {"type": "RESOURCE", "value": "codespaces"}, + {"type": "PARAM", "value": "id"} + ] + + /calls.participants.add → [ + {"type": "RESOURCE", "value": "calls"}, + {"type": "RESOURCE", "value": "participants"}, + {"type": "RESOURCE", "value": "add"} + ] + """ + components = [] + + # Split path on both / and . (for APIs like Slack) + segments = [] + for slash_segment in path.split('/'): + if slash_segment: + # Further split on dots + dot_segments = [s for s in slash_segment.split('.') if s] + segments.extend(dot_segments) + + for segment in segments: + if segment.startswith('{') and segment.endswith('}'): + # Parameter + param_name = segment[1:-1] # Strip braces + components.append({"type": "PARAM", "value": param_name}) + else: + # Resource + components.append({"type": "RESOURCE", "value": segment}) + + return components + + def _detect_container_resources(self, components: List[Dict[str, str]]) -> List[str]: + """ + Identify container resources to skip. + + A resource is a container if there are 2 or more resources after its parameters. + + Example: + /repos/{owner}/{repo}/codespaces/devcontainers + - repos has params {owner}, {repo}, then 2 resources (codespaces, devcontainers) + - repos IS a container → skip it + - codespaces has no params after it, just 1 resource (devcontainers) + - codespaces NOT a container → keep it + + Returns: + List of container resource names to skip + """ + containers = [] + + for i, component in enumerate(components): + if component["type"] != "RESOURCE": + continue + + # Count resources after this resource's parameters + resources_after_params = 0 + found_param = False + + for j in range(i + 1, len(components)): + if components[j]["type"] == "PARAM": + found_param = True + elif components[j]["type"] == "RESOURCE": + if found_param: + # This resource comes after parameters + resources_after_params += 1 + + # If 2+ resources exist after params, this is a container + if resources_after_params >= 2: + containers.append(component["value"]) + + return containers + + def _extract_resource_chain(self, components: List[Dict[str, str]], containers: List[str]) -> List[str]: + """ + Extract resource names, excluding containers and action verbs. + + Args: + components: Parsed path components + containers: List of container resource names to exclude + + Returns: + List of resource names in order, with action verbs stripped from all segments + + Examples: + [calls, participants, add] → [calls, participants] (removed 'add') + [chat, postMessage] → [chat, message] (removed 'post') + [list, conversations] → [conversations] (removed 'list') + [conversations, history] → [conversations, history] (kept - not action verbs) + """ + resources = [] + + for component in components: + if component["type"] == "RESOURCE": + resource_name = component["value"] + if resource_name not in containers: + # Strip action verbs from all resources + resource_name = self._strip_action_verb(resource_name) + # Only add if something remains after stripping + if resource_name: + resources.append(resource_name) + + return resources + + def _strip_action_verb(self, name: str) -> str: + """ + Strip action verb from a resource name. + + Args: + name: Resource name (e.g., 'postMessage', 'add', 'getPermalink') + + Returns: + Name with action verb removed (e.g., 'message', '', 'permalink') + + Examples: + 'add' → '' (pure action verb) + 'postMessage' → 'message' + 'getPermalink' → 'permalink' + 'deleteScheduledMessage' → 'scheduledMessage' + 'history' → 'history' (not an action verb) + """ + name_lower = name.lower() + + # Check if the entire name is an action verb + if name_lower in ACTION_VERBS: + return "" + + # Check for camelCase action verb prefixes (e.g., postMessage, getPermalink) + for verb in ACTION_VERBS: + # Check if starts with verb in camelCase + if name_lower.startswith(verb) and len(name) > len(verb): + # Make sure next char is uppercase (camelCase boundary) + rest = name[len(verb):] + if rest[0].isupper(): + return rest + + return name + + def generate_tool_name( + self, + path: str, + method: str, + path_prefix: Optional[str] = None + ) -> str: + """ + Generate a tool name from a REST API path. + + Args: + path: API path (e.g., '/v1/repos/{owner}/{repo}/issues') + method: HTTP method (GET, POST, PUT, PATCH, DELETE) + path_prefix: Optional path prefix to strip (e.g., '/v1', '/rest/api/3', '/api') + + Returns: + Tool name in camelCase (e.g., 'listRepoIssues') + + Examples: + ('/repos/{owner}/{repo}/issues', 'GET', None) → 'listRepoIssues' + ('/v1/balance/history', 'GET', '/v1') → 'listBalanceHistory' + ('/rest/api/3/issue/{id}', 'DELETE', '/rest/api/3') → 'deleteIssue' + """ + # Step 1: Strip path prefix and file extensions + cleaned_path = self._clean_path(path, path_prefix) + + # Step 2: Parse path into components (resources and parameters) + components = self._parse_path_components(cleaned_path) + + if not components: + raise ValueError(f"Cannot generate tool name: no path components in '{path}'") + + # Step 3: Detect container resources to skip + containers = self._detect_container_resources(components) + + # Step 4: Extract resource chain (excluding containers) + resources = self._extract_resource_chain(components, containers) + + if not resources: + raise ValueError(f"Cannot generate tool name: no resources found in '{path}'") + + # Step 5: Determine if this is a collection endpoint + is_collection = len(components) > 0 and components[-1]["type"] == "RESOURCE" + + # Step 6: Determine verb + verb = self._determine_verb(method, is_collection) + + # Step 7: Apply singularization + resources = self._apply_singularization(resources, verb) + + # Step 8: Apply abbreviations + resources = self._apply_abbreviations(resources) + + # Step 9: Build camelCase name + tool_name = self._build_camel_case_name(verb, resources) + + # Step 10: Validate + if not self._is_valid_tool_name(tool_name): + raise ValueError(f"Generated invalid tool name: {tool_name}") + + return tool_name + + def _determine_verb(self, method: str, is_collection: bool) -> str: + """ + Determine the appropriate verb based on HTTP method and endpoint type. + + Args: + method: HTTP method (GET, POST, PUT, PATCH, DELETE) + is_collection: True if operating on a collection + + Returns: + Verb string (list, get, create, update, delete) + """ + method = method.upper() + + if method == 'GET': + return 'list' if is_collection else 'get' + elif method == 'POST': + return 'create' + elif method in ['PUT', 'PATCH']: + return 'update' + elif method == 'DELETE': + return 'delete' + else: + # Default fallback + return method.lower() + + def _apply_singularization(self, resources: List[str], verb: str) -> List[str]: + """ + Apply inflection rules to resource chain. + + Rules: + - All non-final resources: always singular (repo, not repos) + - Final resource: plural for 'list', singular for everything else + - Uncountable words (data, information, etc.) remain unchanged + + Args: + resources: List of resource names + verb: The operation verb (list, get, create, update, delete) + + Returns: + List of resources with appropriate inflection + + Examples: + ['repos', 'issues'], 'list' → ['repo', 'issues'] + ['repos', 'issues'], 'get' → ['repo', 'issue'] + ['dashboard'], 'list' → ['dashboards'] + ['data'], 'list' → ['data'] (uncountable) + """ + if not resources: + return resources + + # Singularize all non-final resources + inflected = [inflector.singularize(r) for r in resources[:-1]] + + last_resource = resources[-1] + + # Final resource: pluralize for 'list', singularize for everything else + if verb == 'list': + # List operations should have plural resource names + inflected.append(inflector.pluralize(last_resource)) + else: + # Item operations (get, create, update, delete) should be singular + inflected.append(inflector.singularize(last_resource)) + + return inflected + + def _apply_abbreviations(self, resources: List[str]) -> List[str]: + """ + Apply abbreviations from dictionary to resource names. + + Args: + resources: List of resource names + + Returns: + List with abbreviations applied + """ + result = [] + for resource in resources: + # Check for exact match (case-insensitive) + resource_lower = resource.lower() + if resource_lower in self.abbreviations: + result.append(self.abbreviations[resource_lower]) + else: + result.append(resource) + return result + + def _build_camel_case_name(self, verb: str, resources: List[str]) -> str: + """ + Build camelCase tool name from verb and resources. + + Args: + verb: Operation verb + resources: List of resource names + + Returns: + camelCase name + """ + # Start with lowercase verb + parts = [verb.lower()] + + # Add capitalized resources + for resource in resources: + # Handle kebab-case and snake_case in resource names + resource = self._normalize_to_camel_case(resource) + parts.append(resource) + + # Join all parts + return ''.join(parts) + + def _normalize_to_camel_case(self, name: str) -> str: + """ + Normalize a name to have first letter capitalized, rest in camelCase. + Handles kebab-case, snake_case, and PascalCase. + + Args: + name: Name to normalize + + Returns: + Normalized camelCase name with first letter capitalized + """ + if not name: + return name + + # First, split PascalCase/camelCase words + # Insert space before uppercase letters that follow lowercase letters or digits + pascal_split = re.sub(r'([a-z0-9])([A-Z])', r'\1 \2', name) + + # Replace separators (/, _, -, .) with spaces + normalized = re.sub(r'[\/_.-]+', ' ', pascal_split) + + # Split into words and filter out empty strings + words = [word for word in normalized.split(' ') if word] + + if not words: + return name + + # Convert to PascalCase (capitalize each word) + return ''.join(word.capitalize() for word in words) + + def _is_valid_tool_name(self, name: str) -> bool: + """ + Validate that a tool name meets requirements. + + Args: + name: Tool name to validate + + Returns: + True if valid + """ + if not name: + return False + + # Must start with a letter + if not name[0].isalpha(): + return False + + # Must contain at least one alphanumeric character + if not any(c.isalnum() for c in name): + return False + + return True diff --git a/tests/fixtures/test_spec.json b/tests/fixtures/test_spec.json index ade2c76..6a7246d 100644 --- a/tests/fixtures/test_spec.json +++ b/tests/fixtures/test_spec.json @@ -10,7 +10,6 @@ "paths": { "/test": { "get": { - "operationId": "getTest", "summary": "Test endpoint", "responses": { "200": { diff --git a/tests/fixtures/test_spec.yaml b/tests/fixtures/test_spec.yaml index 590bb7f..d892186 100644 --- a/tests/fixtures/test_spec.yaml +++ b/tests/fixtures/test_spec.yaml @@ -7,7 +7,6 @@ servers: paths: /yaml-test: get: - operationId: getYamlTest summary: YAML test endpoint responses: '200': diff --git a/tests/test_inflector.py b/tests/test_inflector.py new file mode 100644 index 0000000..c2288b6 --- /dev/null +++ b/tests/test_inflector.py @@ -0,0 +1,349 @@ +""" +Tests for inflector module - English pluralization and singularization. +""" + +import pytest +from ocp_agent.parsers.inflector import ( + pluralize, + singularize, + is_plural, + is_singular, + is_uncountable, + UNCOUNTABLES, + IRREGULARS +) + + +class TestPluralize: + """Test pluralization functionality.""" + + def test_pluralize_regular_words(self): + """Test pluralization of regular nouns.""" + assert pluralize("repository") == "repositories" + assert pluralize("dashboard") == "dashboards" + assert pluralize("user") == "users" + assert pluralize("issue") == "issues" + assert pluralize("comment") == "comments" + assert pluralize("project") == "projects" + + def test_pluralize_words_ending_in_s(self): + """Test words ending in s, x, z, ch, sh.""" + assert pluralize("class") == "classes" + assert pluralize("box") == "boxes" + assert pluralize("quiz") == "quizzes" + assert pluralize("church") == "churches" + assert pluralize("dish") == "dishes" + assert pluralize("bus") == "buses" + + def test_pluralize_words_ending_in_y(self): + """Test words ending in y.""" + assert pluralize("category") == "categories" + assert pluralize("company") == "companies" + assert pluralize("query") == "queries" + # But not when preceded by vowel + assert pluralize("key") == "keys" + assert pluralize("boy") == "boys" + + def test_pluralize_words_ending_in_f_fe(self): + """Test words ending in f or fe.""" + assert pluralize("knife") == "knives" + assert pluralize("life") == "lives" + assert pluralize("wolf") == "wolves" + + def test_pluralize_words_ending_in_o(self): + """Test words ending in o.""" + assert pluralize("tomato") == "tomatoes" + assert pluralize("potato") == "potatoes" + assert pluralize("buffalo") == "buffaloes" + + def test_pluralize_irregular_words(self): + """Test irregular pluralizations.""" + assert pluralize("person") == "people" + assert pluralize("man") == "men" + assert pluralize("child") == "children" + assert pluralize("mouse") == "mice" + assert pluralize("goose") == "geese" + assert pluralize("foot") == "feet" + assert pluralize("tooth") == "teeth" + assert pluralize("ox") == "oxen" + + def test_pluralize_uncountable_words(self): + """Test uncountable words remain unchanged.""" + assert pluralize("data") == "data" + assert pluralize("information") == "information" + assert pluralize("equipment") == "equipment" + assert pluralize("metadata") == "metadata" + assert pluralize("software") == "software" + assert pluralize("history") == "history" + + def test_pluralize_capitalization(self): + """Test that capitalization is preserved for title case.""" + assert pluralize("Repository") == "Repositories" + assert pluralize("Person") == "People" + # Note: All-caps words don't preserve capitalization perfectly + assert pluralize("User") == "Users" + + def test_pluralize_empty_string(self): + """Test empty string returns empty.""" + assert pluralize("") == "" + + def test_pluralize_already_plural(self): + """Test pluralizing already plural words.""" + assert pluralize("users") == "users" + assert pluralize("repositories") == "repositories" + + +class TestSingularize: + """Test singularization functionality.""" + + def test_singularize_regular_words(self): + """Test singularization of regular nouns.""" + assert singularize("repositories") == "repository" + assert singularize("dashboards") == "dashboard" + assert singularize("users") == "user" + assert singularize("issues") == "issue" + assert singularize("comments") == "comment" + assert singularize("projects") == "project" + + def test_singularize_words_ending_in_es(self): + """Test words ending in es.""" + assert singularize("classes") == "class" + assert singularize("boxes") == "box" + assert singularize("quizzes") == "quiz" + assert singularize("churches") == "church" + assert singularize("dishes") == "dish" + assert singularize("buses") == "bus" + + def test_singularize_words_ending_in_ies(self): + """Test words ending in ies.""" + assert singularize("categories") == "category" + assert singularize("companies") == "company" + assert singularize("queries") == "query" + + def test_singularize_words_ending_in_ves(self): + """Test words ending in ves.""" + assert singularize("knives") == "knife" + assert singularize("lives") == "life" + assert singularize("wolves") == "wolf" + + def test_singularize_words_ending_in_oes(self): + """Test words ending in oes.""" + assert singularize("tomatoes") == "tomato" + assert singularize("potatoes") == "potato" + + def test_singularize_irregular_words(self): + """Test irregular singularizations.""" + assert singularize("people") == "person" + assert singularize("men") == "man" + assert singularize("children") == "child" + assert singularize("mice") == "mouse" + assert singularize("geese") == "goose" + assert singularize("feet") == "foot" + assert singularize("teeth") == "tooth" + assert singularize("oxen") == "ox" + + def test_singularize_uncountable_words(self): + """Test uncountable words remain unchanged.""" + assert singularize("data") == "data" + assert singularize("information") == "information" + assert singularize("equipment") == "equipment" + assert singularize("metadata") == "metadata" + assert singularize("software") == "software" + assert singularize("history") == "history" + + def test_singularize_capitalization(self): + """Test that capitalization is preserved for title case.""" + assert singularize("Repositories") == "Repository" + assert singularize("People") == "Person" + # Note: All-caps words don't preserve capitalization perfectly + assert singularize("Users") == "User" + + def test_singularize_empty_string(self): + """Test empty string returns empty.""" + assert singularize("") == "" + + def test_singularize_already_singular(self): + """Test singularizing already singular words.""" + assert singularize("user") == "user" + assert singularize("repository") == "repository" + + +class TestIsPlural: + """Test is_plural functionality.""" + + def test_is_plural_with_plural_words(self): + """Test that plural words are identified correctly.""" + assert is_plural("users") is True + assert is_plural("repositories") is True + assert is_plural("categories") is True + assert is_plural("people") is True + assert is_plural("children") is True + + def test_is_plural_with_singular_words(self): + """Test that singular words return False.""" + assert is_plural("user") is False + assert is_plural("repository") is False + assert is_plural("category") is False + assert is_plural("person") is False + assert is_plural("child") is False + + def test_is_plural_with_uncountable(self): + """Test that uncountable words return False.""" + assert is_plural("data") is False + assert is_plural("information") is False + assert is_plural("equipment") is False + + def test_is_plural_with_empty_string(self): + """Test empty string returns False.""" + assert is_plural("") is False + + def test_is_plural_edge_cases(self): + """Test edge cases.""" + assert is_plural("news") is False # uncountable + assert is_plural("series") is False # uncountable + assert is_plural("species") is False # uncountable + + +class TestIsSingular: + """Test is_singular functionality.""" + + def test_is_singular_with_singular_words(self): + """Test that singular words are identified correctly.""" + assert is_singular("user") is True + assert is_singular("repository") is True + assert is_singular("category") is True + assert is_singular("person") is True + assert is_singular("child") is True + + def test_is_singular_with_plural_words(self): + """Test that plural words return False.""" + assert is_singular("users") is False + assert is_singular("repositories") is False + assert is_singular("categories") is False + assert is_singular("people") is False + assert is_singular("children") is False + + def test_is_singular_with_uncountable(self): + """Test that uncountable words return False.""" + assert is_singular("data") is False + assert is_singular("information") is False + assert is_singular("equipment") is False + + def test_is_singular_with_empty_string(self): + """Test empty string returns False.""" + assert is_singular("") is False + + def test_is_singular_edge_cases(self): + """Test edge cases.""" + assert is_singular("news") is False # uncountable + assert is_singular("series") is False # uncountable + assert is_singular("species") is False # uncountable + + +class TestIsUncountable: + """Test is_uncountable functionality.""" + + def test_is_uncountable_with_uncountable_words(self): + """Test uncountable words are identified.""" + assert is_uncountable("data") is True + assert is_uncountable("information") is True + assert is_uncountable("equipment") is True + assert is_uncountable("metadata") is True + assert is_uncountable("history") is True + assert is_uncountable("news") is True + assert is_uncountable("series") is True + + def test_is_uncountable_with_countable_words(self): + """Test countable words return False.""" + assert is_uncountable("user") is False + assert is_uncountable("repository") is False + assert is_uncountable("category") is False + + def test_is_uncountable_case_insensitive(self): + """Test that check is case-insensitive.""" + assert is_uncountable("Data") is True + assert is_uncountable("DATA") is True + assert is_uncountable("Information") is True + + +class TestRoundTrip: + """Test round-trip conversions.""" + + def test_singular_to_plural_to_singular(self): + """Test converting singular→plural→singular.""" + words = ["user", "repository", "category", "person", "child"] + for word in words: + plural = pluralize(word) + back_to_singular = singularize(plural) + assert back_to_singular == word, f"{word} → {plural} → {back_to_singular}" + + def test_plural_to_singular_to_plural(self): + """Test converting plural→singular→plural.""" + words = ["users", "repositories", "categories", "people", "children"] + for word in words: + singular = singularize(word) + back_to_plural = pluralize(singular) + assert back_to_plural == word, f"{word} → {singular} → {back_to_plural}" + + +class TestAPISpecificWords: + """Test words commonly found in API paths.""" + + def test_api_resource_names(self): + """Test common API resource names.""" + # Singularize + assert singularize("repos") == "repo" + assert singularize("issues") == "issue" + assert singularize("gists") == "gist" + assert singularize("orgs") == "org" + assert singularize("webhooks") == "webhook" + assert singularize("endpoints") == "endpoint" + assert singularize("branches") == "branch" + + # Pluralize + assert pluralize("repo") == "repos" + assert pluralize("issue") == "issues" + assert pluralize("gist") == "gists" + assert pluralize("org") == "orgs" + assert pluralize("webhook") == "webhooks" + assert pluralize("endpoint") == "endpoints" + assert pluralize("branch") == "branches" + + def test_tech_terms(self): + """Test technology-specific terms.""" + # Special handling + assert singularize("analyses") == "analysis" + assert pluralize("analysis") == "analyses" + + # Uncountables + assert pluralize("metadata") == "metadata" + assert singularize("metadata") == "metadata" + assert pluralize("data") == "data" + assert singularize("data") == "data" + + +class TestEdgeCases: + """Test edge cases and special scenarios.""" + + def test_words_ending_in_ss(self): + """Test words that end in ss.""" + assert pluralize("class") == "classes" + assert singularize("classes") == "class" + assert pluralize("pass") == "passes" + assert singularize("passes") == "pass" + + def test_acronyms_and_abbreviations(self): + """Test acronyms and abbreviations.""" + assert pluralize("API") == "APIs" + assert pluralize("URL") == "URLs" + assert pluralize("ID") == "IDs" + + def test_compound_words(self): + """Test compound words.""" + assert pluralize("webhook") == "webhooks" + assert singularize("webhooks") == "webhook" + + def test_words_with_numbers(self): + """Test words containing numbers.""" + assert pluralize("v2endpoint") == "v2endpoints" + assert singularize("v2endpoints") == "v2endpoint" diff --git a/tests/test_openapi_parser.py b/tests/test_openapi_parser.py index f431b33..7014078 100644 --- a/tests/test_openapi_parser.py +++ b/tests/test_openapi_parser.py @@ -252,38 +252,6 @@ def test_swagger2_body_parameters(self, parser, swagger2_spec): assert "email" in post_tool.parameters assert post_tool.parameters["name"]["location"] == "body" - # Tool name normalization tests - def test_normalize_tool_name_slash_separators(self, parser): - """Test normalization of operationId with slash separators.""" - assert parser._normalize_tool_name("meta/root") == "metaRoot" - assert parser._normalize_tool_name("repos/disable-vulnerability-alerts") == "reposDisableVulnerabilityAlerts" - - def test_normalize_tool_name_underscore_separators(self, parser): - """Test normalization of operationId with underscore separators.""" - assert parser._normalize_tool_name("admin_apps_approve") == "adminAppsApprove" - assert parser._normalize_tool_name("get_users_list") == "getUsersList" - - def test_normalize_tool_name_pascal_case(self, parser): - """Test normalization of PascalCase operationIds.""" - assert parser._normalize_tool_name("FetchAccount") == "fetchAccount" - assert parser._normalize_tool_name("GetUserProfile") == "getUserProfile" - - def test_normalize_tool_name_numbers(self, parser): - """Test that numbers are preserved in normalization.""" - assert parser._normalize_tool_name("v2010/Accounts") == "v2010Accounts" - - def test_normalize_tool_name_acronyms(self, parser): - """Test that acronyms are handled.""" - assert parser._normalize_tool_name("SMS/send") == "smsSend" - - def test_valid_tool_name(self, parser): - """Test tool name validation.""" - assert parser._is_valid_tool_name("metaRoot") is True - assert parser._is_valid_tool_name("getUsersList") is True - assert parser._is_valid_tool_name("") is False - assert parser._is_valid_tool_name("123invalid") is False - assert parser._is_valid_tool_name("___") is False - # Resource filtering tests def test_filter_tools_by_resources(self, parser): """Test filtering tools by resource names.""" diff --git a/tests/test_schema_discovery.py b/tests/test_schema_discovery.py index 12941fb..d5d172d 100644 --- a/tests/test_schema_discovery.py +++ b/tests/test_schema_discovery.py @@ -139,129 +139,6 @@ def sample_openapi_spec(self): } } - @pytest.fixture - def openapi_spec_with_operation_ids(self): - """OpenAPI spec with various operationId patterns for testing normalization.""" - return { - "openapi": "3.0.0", - "info": { - "title": "Test API with Operation IDs", - "version": "1.0.0" - }, - "servers": [ - {"url": "https://api.example.com"} - ], - "paths": { - "/meta": { - "get": { - "operationId": "meta/root", # GitHub pattern - slash separator - "summary": "Get API root", - "description": "Get API metadata" - } - }, - "/repos/alerts": { - "post": { - "operationId": "repos/disable-vulnerability-alerts", # GitHub pattern - multiple words - "summary": "Disable vulnerability alerts", - "description": "Disable vulnerability alerts for repository" - } - }, - "/admin/apps": { - "put": { - "operationId": "admin_apps_approve", # Slack pattern - underscore separator - "summary": "Approve app", - "description": "Approve an application" - } - }, - "/accounts": { - "get": { - "operationId": "FetchAccount", # Twilio pattern - PascalCase - "summary": "Fetch account", - "description": "Fetch account details" - }, - "post": { - "operationId": "CreateAccount", # Twilio pattern - PascalCase - "summary": "Create account", - "description": "Create new account" - } - }, - "/v2010/accounts": { - "get": { - "operationId": "v2010/Accounts", # Version number with slash - "summary": "List v2010 accounts", - "description": "List accounts from v2010 API" - } - }, - "/sms": { - "post": { - "operationId": "SMS/send", # Acronym preservation test - "summary": "Send SMS", - "description": "Send SMS message" - } - }, - "/users/no-operation-id": { - "get": { - # No operationId - should use fallback naming - "summary": "Get users without operation ID", - "description": "Test fallback naming when no operationId present" - } - }, - "/api//double-slash": { - "get": { - "operationId": "api//users", # Multiple consecutive separators - "summary": "Test double slash", - "description": "Test handling of multiple separators" - } - } - } - } - - @pytest.fixture - def openapi_spec_edge_cases(self): - """OpenAPI spec with edge cases for testing robustness.""" - return { - "openapi": "3.0.0", - "info": { - "title": "Edge Cases API", - "version": "1.0.0" - }, - "servers": [ - {"url": "https://api.example.com"} - ], - "paths": { - "/empty-operation-id": { - "get": { - "operationId": "", # Empty operationId - "summary": "Empty operation ID test" - } - }, - "/single-char": { - "get": { - "operationId": "a", # Single character - "summary": "Single character test" - } - }, - "/no-operation-id": { - "get": { - # No operationId - should use fallback naming - "summary": "Missing operationId test" - } - }, - "/mixed-separators": { - "get": { - "operationId": "api-v1/users_list.all", # Mixed separator types - "summary": "Mixed separators test" - } - }, - "/preserve-acronyms": { - "get": { - "operationId": "get_API_HTTP_URL", # Multiple acronyms - "summary": "Acronym preservation test" - } - } - } - } - @patch('requests.get') def test_discover_api_success(self, mock_get, discovery, sample_openapi_spec): """Test successful API discovery.""" @@ -409,7 +286,6 @@ def test_discover_api_with_refs(self, mock_get, discovery): "paths": { "/queue": { "post": { - "operationId": "updateQueue", "summary": "Update queue", "responses": { "200": { @@ -454,7 +330,7 @@ def test_discover_api_with_refs(self, mock_get, discovery): tool = api_spec.tools[0] # Verify the $ref was resolved - assert tool.name == "updateQueue" + assert tool.name == "createQueue" assert tool.response_schema is not None assert tool.response_schema.get("type") == "object" assert "properties" in tool.response_schema @@ -475,7 +351,6 @@ def test_discover_api_with_circular_refs(self, mock_get, discovery): "paths": { "/node": { "get": { - "operationId": "getNode", "summary": "Get node", "responses": { "200": { @@ -713,7 +588,7 @@ def test_load_json_file_absolute_path(self, discovery): assert api_spec.title == "Test API from File" assert api_spec.version == "1.0.0" assert len(api_spec.tools) == 1 - assert api_spec.tools[0].name == "getTest" + assert api_spec.tools[0].name == "listTests" def test_load_json_file_relative_path(self, discovery): """Test loading JSON spec from relative file path.""" @@ -733,7 +608,7 @@ def test_load_yaml_file(self, discovery): assert api_spec.title == "Test API from YAML" assert api_spec.version == "1.0.0" assert len(api_spec.tools) == 1 - assert api_spec.tools[0].name == "getYamlTest" + assert api_spec.tools[0].name == "listYamlTests" def test_file_not_found(self, discovery): """Test error when file doesn't exist.""" diff --git a/tests/test_tool_naming.py b/tests/test_tool_naming.py new file mode 100644 index 0000000..d772c54 --- /dev/null +++ b/tests/test_tool_naming.py @@ -0,0 +1,597 @@ +""" +Tests for tool naming engine - REST path to tool name generation. +""" + +import pytest +from ocp_agent.parsers.tool_naming import ToolNamingEngine, ACTION_VERBS + + +class TestToolNamingEngineInitialization: + """Test initialization and configuration.""" + + def test_init_with_default_abbreviations(self): + """Test initialization loads default abbreviations.""" + engine = ToolNamingEngine() + assert engine.abbreviations is not None + assert isinstance(engine.abbreviations, dict) + # Check for some known abbreviations + assert "application" in engine.abbreviations + assert engine.abbreviations["application"] == "app" + + def test_init_with_custom_abbreviations(self): + """Test initialization with custom abbreviations.""" + custom_abbrs = {"repository": "repo", "organization": "org"} + engine = ToolNamingEngine(abbreviations=custom_abbrs) + assert engine.abbreviations == custom_abbrs + + def test_init_with_empty_abbreviations(self): + """Test initialization with empty abbreviations dict.""" + engine = ToolNamingEngine(abbreviations={}) + assert engine.abbreviations == {} + + +class TestCleanPath: + """Test path cleaning functionality.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_clean_path_with_prefix(self, engine): + """Test stripping path prefixes.""" + assert engine._clean_path("/v1/balance", "/v1") == "/balance" + assert engine._clean_path("/rest/api/3/issue", "/rest/api/3") == "/issue" + assert engine._clean_path("/api/users", "/api") == "/users" + + def test_clean_path_case_insensitive_prefix(self, engine): + """Test that prefix stripping is case-insensitive.""" + assert engine._clean_path("/V1/balance", "/v1") == "/balance" + assert engine._clean_path("/v1/balance", "/V1") == "/balance" + assert engine._clean_path("/API/users", "/api") == "/users" + + def test_clean_path_with_file_extensions(self, engine): + """Test stripping file extensions.""" + assert engine._clean_path("/Accounts.json") == "/Accounts" + assert engine._clean_path("/Messages.xml") == "/Messages" + assert engine._clean_path("/config.yaml") == "/config" + assert engine._clean_path("/data.yml") == "/data" + + def test_clean_path_with_prefix_and_extension(self, engine): + """Test stripping both prefix and extension.""" + result = engine._clean_path("/2010-04-01/Accounts/{Sid}/Calls.json", "/2010-04-01/Accounts/{Sid}") + assert result == "/Calls" + + def test_clean_path_no_prefix(self, engine): + """Test cleaning without prefix.""" + assert engine._clean_path("/users") == "/users" + assert engine._clean_path("/repos/{id}") == "/repos/{id}" + + def test_clean_path_empty(self, engine): + """Test with empty path.""" + assert engine._clean_path("") == "" + + +class TestParsePathComponents: + """Test path component parsing.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_parse_simple_path(self, engine): + """Test parsing simple paths.""" + components = engine._parse_path_components("/users") + assert components == [{"type": "RESOURCE", "value": "users"}] + + def test_parse_path_with_param(self, engine): + """Test parsing paths with parameters.""" + components = engine._parse_path_components("/users/{id}") + assert components == [ + {"type": "RESOURCE", "value": "users"}, + {"type": "PARAM", "value": "id"} + ] + + def test_parse_nested_path(self, engine): + """Test parsing nested resource paths.""" + components = engine._parse_path_components("/repos/{owner}/{repo}/issues") + assert components == [ + {"type": "RESOURCE", "value": "repos"}, + {"type": "PARAM", "value": "owner"}, + {"type": "PARAM", "value": "repo"}, + {"type": "RESOURCE", "value": "issues"} + ] + + def test_parse_path_with_dots(self, engine): + """Test parsing Slack-style dot notation.""" + components = engine._parse_path_components("/chat.postMessage") + assert components == [ + {"type": "RESOURCE", "value": "chat"}, + {"type": "RESOURCE", "value": "postMessage"} + ] + + components = engine._parse_path_components("/calls.participants.add") + assert components == [ + {"type": "RESOURCE", "value": "calls"}, + {"type": "RESOURCE", "value": "participants"}, + {"type": "RESOURCE", "value": "add"} + ] + + def test_parse_path_mixed_separators(self, engine): + """Test parsing with mixed / and . separators.""" + components = engine._parse_path_components("/api/conversations.history") + assert components == [ + {"type": "RESOURCE", "value": "api"}, + {"type": "RESOURCE", "value": "conversations"}, + {"type": "RESOURCE", "value": "history"} + ] + + def test_parse_empty_path(self, engine): + """Test parsing empty path.""" + components = engine._parse_path_components("/") + assert components == [] + + +class TestDetectContainerResources: + """Test container resource detection.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_detect_container_basic(self, engine): + """Test basic container detection.""" + # /repos/{owner}/{repo}/codespaces/devcontainers + # repos has 2+ resources after params → container + components = [ + {"type": "RESOURCE", "value": "repos"}, + {"type": "PARAM", "value": "owner"}, + {"type": "PARAM", "value": "repo"}, + {"type": "RESOURCE", "value": "codespaces"}, + {"type": "RESOURCE", "value": "devcontainers"} + ] + containers = engine._detect_container_resources(components) + assert "repos" in containers + assert "codespaces" not in containers + + def test_detect_no_containers(self, engine): + """Test when there are no containers.""" + # /users/{id} + components = [ + {"type": "RESOURCE", "value": "users"}, + {"type": "PARAM", "value": "id"} + ] + containers = engine._detect_container_resources(components) + assert len(containers) == 0 + + def test_detect_multiple_levels(self, engine): + """Test multi-level resource hierarchy.""" + # /orgs/{org}/repos/{repo}/issues + # orgs has 2 resources after params → container + components = [ + {"type": "RESOURCE", "value": "orgs"}, + {"type": "PARAM", "value": "org"}, + {"type": "RESOURCE", "value": "repos"}, + {"type": "PARAM", "value": "repo"}, + {"type": "RESOURCE", "value": "issues"} + ] + containers = engine._detect_container_resources(components) + assert "orgs" in containers + assert "repos" not in containers + + +class TestStripActionVerb: + """Test action verb stripping.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_strip_pure_action_verbs(self, engine): + """Test stripping when entire word is an action verb.""" + assert engine._strip_action_verb("add") == "" + assert engine._strip_action_verb("remove") == "" + assert engine._strip_action_verb("create") == "" + assert engine._strip_action_verb("delete") == "" + assert engine._strip_action_verb("list") == "" + + def test_strip_camelcase_action_verbs(self, engine): + """Test stripping action verbs from camelCase names.""" + assert engine._strip_action_verb("postMessage") == "Message" + assert engine._strip_action_verb("getMessage") == "Message" + assert engine._strip_action_verb("createAccount") == "Account" + assert engine._strip_action_verb("deleteScheduledMessage") == "ScheduledMessage" + assert engine._strip_action_verb("updateQueue") == "Queue" + + def test_strip_no_action_verb(self, engine): + """Test when no action verb is present.""" + assert engine._strip_action_verb("history") == "history" + assert engine._strip_action_verb("participants") == "participants" + assert engine._strip_action_verb("message") == "message" + assert engine._strip_action_verb("account") == "account" + + def test_strip_case_insensitive(self, engine): + """Test that verb detection is case-insensitive.""" + assert engine._strip_action_verb("postMessage") == "Message" + assert engine._strip_action_verb("PostMessage") == "Message" + assert engine._strip_action_verb("POST") == "" + + +class TestExtractResourceChain: + """Test resource chain extraction.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_extract_simple_chain(self, engine): + """Test extracting simple resource chain.""" + components = [ + {"type": "RESOURCE", "value": "users"}, + {"type": "PARAM", "value": "id"} + ] + resources = engine._extract_resource_chain(components, []) + assert resources == ["users"] + + def test_extract_with_containers(self, engine): + """Test extraction excludes containers.""" + components = [ + {"type": "RESOURCE", "value": "repos"}, + {"type": "PARAM", "value": "owner"}, + {"type": "RESOURCE", "value": "issues"} + ] + containers = ["repos"] + resources = engine._extract_resource_chain(components, containers) + assert resources == ["issues"] + + def test_extract_with_action_verbs(self, engine): + """Test that action verbs are stripped.""" + components = [ + {"type": "RESOURCE", "value": "calls"}, + {"type": "RESOURCE", "value": "participants"}, + {"type": "RESOURCE", "value": "add"} + ] + resources = engine._extract_resource_chain(components, []) + assert resources == ["calls", "participants"] + assert "add" not in resources + + def test_extract_slack_style(self, engine): + """Test extraction from Slack-style paths.""" + components = [ + {"type": "RESOURCE", "value": "chat"}, + {"type": "RESOURCE", "value": "postMessage"} + ] + resources = engine._extract_resource_chain(components, []) + assert resources == ["chat", "Message"] + + +class TestDetermineVerb: + """Test HTTP verb determination.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_determine_verb_get_collection(self, engine): + """Test GET on collection returns 'list'.""" + assert engine._determine_verb("GET", is_collection=True) == "list" + + def test_determine_verb_get_item(self, engine): + """Test GET on item returns 'get'.""" + assert engine._determine_verb("GET", is_collection=False) == "get" + + def test_determine_verb_post(self, engine): + """Test POST returns 'create'.""" + assert engine._determine_verb("POST", is_collection=True) == "create" + assert engine._determine_verb("POST", is_collection=False) == "create" + + def test_determine_verb_put_patch(self, engine): + """Test PUT/PATCH return 'update'.""" + assert engine._determine_verb("PUT", is_collection=False) == "update" + assert engine._determine_verb("PATCH", is_collection=False) == "update" + + def test_determine_verb_delete(self, engine): + """Test DELETE returns 'delete'.""" + assert engine._determine_verb("DELETE", is_collection=False) == "delete" + + +class TestApplySingularization: + """Test inflection application.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_singularize_non_final_resources(self, engine): + """Test that non-final resources are singularized.""" + resources = ["repos", "issues"] + result = engine._apply_singularization(resources, "get") + assert result == ["repo", "issue"] + + def test_pluralize_final_for_list(self, engine): + """Test that final resource is pluralized for 'list'.""" + resources = ["repo", "issue"] + result = engine._apply_singularization(resources, "list") + assert result == ["repo", "issues"] + + def test_singularize_final_for_get(self, engine): + """Test that final resource is singularized for item operations.""" + resources = ["repos", "issues"] + result = engine._apply_singularization(resources, "get") + assert result == ["repo", "issue"] + + result = engine._apply_singularization(resources, "create") + assert result == ["repo", "issue"] + + result = engine._apply_singularization(resources, "update") + assert result == ["repo", "issue"] + + def test_uncountable_words(self, engine): + """Test that uncountable words remain unchanged.""" + resources = ["data"] + result = engine._apply_singularization(resources, "list") + assert result == ["data"] + + result = engine._apply_singularization(resources, "get") + assert result == ["data"] + + +class TestApplyAbbreviations: + """Test abbreviation application.""" + + def test_apply_abbreviations(self): + """Test that abbreviations are applied.""" + engine = ToolNamingEngine(abbreviations={"application": "app", "repository": "repo"}) + resources = ["application", "repository"] + result = engine._apply_abbreviations(resources) + assert result == ["app", "repo"] + + def test_case_insensitive_lookup(self): + """Test that abbreviation lookup is case-insensitive.""" + engine = ToolNamingEngine(abbreviations={"application": "app"}) + resources = ["Application", "APPLICATION"] + result = engine._apply_abbreviations(resources) + assert result == ["app", "app"] + + def test_no_abbreviation(self): + """Test resources without abbreviations.""" + engine = ToolNamingEngine(abbreviations={"application": "app"}) + resources = ["user", "issue"] + result = engine._apply_abbreviations(resources) + assert result == ["user", "issue"] + + +class TestBuildCamelCaseName: + """Test camelCase name building.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_build_simple_name(self, engine): + """Test building simple camelCase names.""" + result = engine._build_camel_case_name("list", ["users"]) + assert result == "listUsers" + + result = engine._build_camel_case_name("get", ["user"]) + assert result == "getUser" + + def test_build_nested_name(self, engine): + """Test building nested resource names.""" + result = engine._build_camel_case_name("list", ["repo", "issues"]) + assert result == "listRepoIssues" + + result = engine._build_camel_case_name("create", ["gist", "comment"]) + assert result == "createGistComment" + + def test_build_with_kebab_case(self, engine): + """Test handling kebab-case in resources.""" + result = engine._build_camel_case_name("list", ["scheduled-messages"]) + assert result == "listScheduledMessages" + + def test_build_with_snake_case(self, engine): + """Test handling snake_case in resources.""" + result = engine._build_camel_case_name("list", ["balance_transactions"]) + assert result == "listBalanceTransactions" + + +class TestNormalizeToCamelCase: + """Test camelCase normalization.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_normalize_kebab_case(self, engine): + """Test normalizing kebab-case.""" + assert engine._normalize_to_camel_case("scheduled-messages") == "ScheduledMessages" + assert engine._normalize_to_camel_case("balance-history") == "BalanceHistory" + + def test_normalize_snake_case(self, engine): + """Test normalizing snake_case.""" + assert engine._normalize_to_camel_case("balance_transactions") == "BalanceTransactions" + assert engine._normalize_to_camel_case("user_profile") == "UserProfile" + + def test_normalize_pascal_case(self, engine): + """Test normalizing PascalCase.""" + assert engine._normalize_to_camel_case("UserProfile") == "UserProfile" + assert engine._normalize_to_camel_case("BalanceHistory") == "BalanceHistory" + + def test_normalize_already_camel_case(self, engine): + """Test normalizing already camelCase words.""" + assert engine._normalize_to_camel_case("userProfile") == "UserProfile" + assert engine._normalize_to_camel_case("balanceHistory") == "BalanceHistory" + + def test_normalize_with_dots(self, engine): + """Test normalizing with dots.""" + assert engine._normalize_to_camel_case("scheduled.messages") == "ScheduledMessages" + + def test_normalize_empty(self, engine): + """Test normalizing empty string.""" + assert engine._normalize_to_camel_case("") == "" + + +class TestIsValidToolName: + """Test tool name validation.""" + + @pytest.fixture + def engine(self): + return ToolNamingEngine(abbreviations={}) + + def test_valid_names(self, engine): + """Test valid tool names.""" + assert engine._is_valid_tool_name("listUsers") is True + assert engine._is_valid_tool_name("getUser") is True + assert engine._is_valid_tool_name("createRepoIssue") is True + assert engine._is_valid_tool_name("a") is True + + def test_invalid_empty(self, engine): + """Test empty names are invalid.""" + assert engine._is_valid_tool_name("") is False + + def test_invalid_starts_with_number(self, engine): + """Test names starting with numbers are invalid.""" + assert engine._is_valid_tool_name("123user") is False + assert engine._is_valid_tool_name("1list") is False + + def test_invalid_no_alphanumeric(self, engine): + """Test names without alphanumeric chars are invalid.""" + assert engine._is_valid_tool_name("___") is False + assert engine._is_valid_tool_name("---") is False + + +class TestGenerateToolName: + """Test end-to-end tool name generation.""" + + @pytest.fixture + def engine(self): + # Use default abbreviations from bundled dictionary + return ToolNamingEngine() + + def test_generate_simple_collection(self, engine): + """Test generating name for simple collection.""" + name = engine.generate_tool_name("/users", "GET") + assert name == "listUsers" + + def test_generate_simple_item(self, engine): + """Test generating name for simple item.""" + name = engine.generate_tool_name("/users/{id}", "GET") + assert name == "getUser" + + def test_generate_nested_resources(self, engine): + """Test generating name for nested resources.""" + name = engine.generate_tool_name("/repos/{owner}/{repo}/issues", "GET") + assert name == "listRepoIssues" + + name = engine.generate_tool_name("/repos/{owner}/{repo}/issues/{id}", "GET") + assert name == "getRepoIssue" + + def test_generate_with_path_prefix(self, engine): + """Test generating name with path prefix stripping.""" + name = engine.generate_tool_name("/v1/balance", "GET", path_prefix="/v1") + assert name == "listBalances" + + name = engine.generate_tool_name("/rest/api/3/issue/{id}", "DELETE", path_prefix="/rest/api/3") + assert name == "deleteIssue" + + def test_generate_post_method(self, engine): + """Test generating names for POST requests.""" + name = engine.generate_tool_name("/users", "POST") + assert name == "createUser" + + name = engine.generate_tool_name("/repos/{owner}/{repo}/issues", "POST") + assert name == "createRepoIssue" + + def test_generate_delete_method(self, engine): + """Test generating names for DELETE requests.""" + name = engine.generate_tool_name("/users/{id}", "DELETE") + assert name == "deleteUser" + + def test_generate_update_methods(self, engine): + """Test generating names for PUT/PATCH requests.""" + name = engine.generate_tool_name("/users/{id}", "PUT") + assert name == "updateUser" + + name = engine.generate_tool_name("/users/{id}", "PATCH") + assert name == "updateUser" + + def test_generate_with_abbreviations(self, engine): + """Test that abbreviations are applied.""" + name = engine.generate_tool_name("/applications", "GET") + assert name == "listApps" + + name = engine.generate_tool_name("/repositories/{id}", "GET") + assert name == "getRepo" + + def test_generate_slack_dot_notation(self, engine): + """Test Slack-style dot notation paths.""" + name = engine.generate_tool_name("/chat.postMessage", "POST") + assert name == "createChatMsg" # Message abbreviated to Msg + + name = engine.generate_tool_name("/conversations.history", "GET") + assert name == "listConversationHistory" + + def test_generate_with_file_extensions(self, engine): + """Test paths with file extensions.""" + name = engine.generate_tool_name("/Accounts.json", "POST", path_prefix=None) + assert name == "createAccount" + + def test_generate_container_skipping(self, engine): + """Test that container resources are skipped.""" + # repos is container, codespaces and devcontainers are kept + name = engine.generate_tool_name( + "/repos/{owner}/{repo}/codespaces/devcontainers", + "GET" + ) + assert name == "listCodespaceDevcontainers" + + def test_generate_error_no_components(self, engine): + """Test error when path has no components.""" + with pytest.raises(ValueError, match="no path components"): + engine.generate_tool_name("/", "GET") + + def test_generate_error_no_resources(self, engine): + """Test error when path has no resources.""" + # This shouldn't happen in practice, but test the error handling + with pytest.raises(ValueError, match="no resources found"): + # Paths with only action verbs would fail + engine.generate_tool_name("/add", "POST") + + +class TestRealWorldExamples: + """Test with real-world API paths.""" + + @pytest.fixture + def engine(self): + # Use default abbreviations from bundled dictionary + return ToolNamingEngine() + + def test_github_paths(self, engine): + """Test GitHub API paths.""" + assert engine.generate_tool_name("/gists", "GET") == "listGists" + assert engine.generate_tool_name("/gists/{id}", "GET") == "getGist" + assert engine.generate_tool_name("/gists/{gist_id}/comments", "GET") == "listGistComments" + assert engine.generate_tool_name("/gists/{gist_id}/comments/{id}", "DELETE") == "deleteGistComment" + + def test_stripe_paths(self, engine): + """Test Stripe API paths.""" + assert engine.generate_tool_name("/v1/balance", "GET", "/v1") == "listBalances" + assert engine.generate_tool_name("/v1/charges/{id}/refunds", "GET", "/v1") == "listChargeRefunds" + assert engine.generate_tool_name("/v1/payment_intents", "POST", "/v1") == "createPaymentIntent" + + def test_twilio_paths(self, engine): + """Test Twilio API paths.""" + prefix = "/2010-04-01/Accounts/{AccountSid}" + assert engine.generate_tool_name(f"{prefix}/Calls.json", "GET", prefix) == "listCalls" + assert engine.generate_tool_name(f"{prefix}/Messages.json", "POST", prefix) == "createMsg" + + def test_jira_paths(self, engine): + """Test Jira API paths.""" + prefix = "/rest/api/3" + assert engine.generate_tool_name(f"{prefix}/dashboard", "GET", prefix) == "listDashboards" + assert engine.generate_tool_name(f"{prefix}/issue/{{id}}", "DELETE", prefix) == "deleteIssue" + + def test_confluence_paths(self, engine): + """Test Confluence API paths.""" + prefix = "/wiki/api/v2" + assert engine.generate_tool_name(f"{prefix}/attachments", "GET", prefix) == "listAttachments" + assert engine.generate_tool_name(f"{prefix}/blogposts", "POST", prefix) == "createBlogpost" From 0b687c29eaa0f30c72774887463843ab1bfa11c5 Mon Sep 17 00:00:00 2001 From: Nathan Allen Date: Sat, 7 Mar 2026 11:05:22 -0500 Subject: [PATCH 2/6] chore: remove stale braid files for refresh --- .braids.json | 17 -- src/ocp_agent/dictionaries/abbrs.json | 189 ------------------ src/ocp_agent/schemas/ocp-context.json | 138 ------------- .../schemas/ocp-openapi-extensions.json | 106 ---------- src/ocp_agent/schemas/ocp-tool.json | 174 ---------------- 5 files changed, 624 deletions(-) delete mode 100644 .braids.json delete mode 100644 src/ocp_agent/dictionaries/abbrs.json delete mode 100644 src/ocp_agent/schemas/ocp-context.json delete mode 100644 src/ocp_agent/schemas/ocp-openapi-extensions.json delete mode 100644 src/ocp_agent/schemas/ocp-tool.json diff --git a/.braids.json b/.braids.json deleted file mode 100644 index 5fbfac9..0000000 --- a/.braids.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "config_version": 1, - "mirrors": { - "src/ocp_agent/schemas": { - "url": "https://github.com/opencontextprotocol/ocp-spec.git", - "branch": "main", - "path": "schemas", - "revision": "b1ba770eb3e1bd245928088f1e7be0516d0fe820" - }, - "src/ocp_agent/dictionaries": { - "url": "https://github.com/opencontextprotocol/ocp-registry.git", - "branch": "main", - "path": "data/dictionaries", - "revision": "HEAD" - } - } -} diff --git a/src/ocp_agent/dictionaries/abbrs.json b/src/ocp_agent/dictionaries/abbrs.json deleted file mode 100644 index 6e871f2..0000000 --- a/src/ocp_agent/dictionaries/abbrs.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "version": "0.1.0", - "abbreviations": [ - { - "word": "repository", - "abbr": "repo" - }, - { - "word": "repositories", - "abbr": "repos" - }, - { - "word": "configuration", - "abbr": "config" - }, - { - "word": "application", - "abbr": "app" - }, - { - "word": "applications", - "abbr": "apps" - }, - { - "word": "authentication", - "abbr": "auth" - }, - { - "word": "authorization", - "abbr": "authz" - }, - { - "word": "administrator", - "abbr": "admin" - }, - { - "word": "administrators", - "abbr": "admins" - }, - { - "word": "environment", - "abbr": "env" - }, - { - "word": "environments", - "abbr": "envs" - }, - { - "word": "organization", - "abbr": "org" - }, - { - "word": "organizations", - "abbr": "orgs" - }, - { - "word": "information", - "abbr": "info" - }, - { - "word": "description", - "abbr": "desc" - }, - { - "word": "specification", - "abbr": "spec" - }, - { - "word": "specifications", - "abbr": "specs" - }, - { - "word": "parameter", - "abbr": "param" - }, - { - "word": "parameters", - "abbr": "params" - }, - { - "word": "argument", - "abbr": "arg" - }, - { - "word": "arguments", - "abbr": "args" - }, - { - "word": "reference", - "abbr": "ref" - }, - { - "word": "references", - "abbr": "refs" - }, - { - "word": "attribute", - "abbr": "attr" - }, - { - "word": "attributes", - "abbr": "attrs" - }, - { - "word": "statistic", - "abbr": "stat" - }, - { - "word": "statistics", - "abbr": "stats" - }, - { - "word": "document", - "abbr": "doc" - }, - { - "word": "documents", - "abbr": "docs" - }, - { - "word": "message", - "abbr": "msg" - }, - { - "word": "messages", - "abbr": "msgs" - }, - { - "word": "database", - "abbr": "db" - }, - { - "word": "databases", - "abbr": "dbs" - }, - { - "word": "temporary", - "abbr": "temp" - }, - { - "word": "maximum", - "abbr": "max" - }, - { - "word": "minimum", - "abbr": "min" - }, - { - "word": "kubernetes", - "abbr": "k8s" - }, - { - "word": "internationalization", - "abbr": "i18n" - }, - { - "word": "localization", - "abbr": "l10n" - }, - { - "word": "metadata", - "abbr": "meta" - }, - { - "word": "identifier", - "abbr": "id" - }, - { - "word": "identifiers", - "abbr": "ids" - }, - { - "word": "variable", - "abbr": "var" - }, - { - "word": "variables", - "abbr": "vars" - }, - { - "word": "package", - "abbr": "pkg" - }, - { - "word": "packages", - "abbr": "pkgs" - } - ] -} diff --git a/src/ocp_agent/schemas/ocp-context.json b/src/ocp_agent/schemas/ocp-context.json deleted file mode 100644 index 22df43d..0000000 --- a/src/ocp_agent/schemas/ocp-context.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://opencontextprotocol.org/schemas/ocp-context.json", - "title": "OCP Context Object", - "description": "Agent context for Open Context Protocol", - "type": "object", - "required": [ - "context_id", - "agent_type", - "created_at", - "last_updated" - ], - "properties": { - "context_id": { - "type": "string", - "pattern": "^ocp-[a-f0-9]{8,}$", - "description": "Unique identifier for this context" - }, - "agent_type": { - "type": "string", - "description": "Type of agent creating this context" - }, - "user": { - "type": ["string", "null"], - "description": "User identifier" - }, - "workspace": { - "type": ["string", "null"], - "description": "Current workspace or project" - }, - "current_file": { - "type": ["string", "null"], - "description": "Currently active file" - }, - "current_goal": { - "type": ["string", "null"], - "description": "Agent's current objective" - }, - "context_summary": { - "type": ["string", "null"], - "description": "Brief summary of conversation context" - }, - "error_context": { - "type": ["string", "null"], - "description": "Error information for debugging" - }, - "recent_changes": { - "type": "array", - "items": { - "type": "string" - }, - "maxItems": 10, - "description": "Recent changes or modifications (max 10)", - "default": [] - }, - "session": { - "type": "object", - "description": "Session tracking information for this context", - "properties": { - "start_time": { - "type": "string", - "format": "date-time", - "description": "Session start timestamp" - }, - "interaction_count": { - "type": "integer", - "minimum": 0, - "default": 0, - "description": "Number of interactions in this session" - }, - "agent_type": { - "type": "string", - "description": "Type of agent for this session" - } - }, - "required": ["start_time", "interaction_count", "agent_type"], - "additionalProperties": true, - "default": {} - }, - "history": { - "type": "array", - "description": "Chronological record of actions and API calls", - "items": { - "type": "object", - "properties": { - "timestamp": { - "type": "string", - "format": "date-time", - "description": "When this action occurred" - }, - "action": { - "type": "string", - "description": "Type of action performed" - }, - "api_endpoint": { - "type": ["string", "null"], - "description": "API endpoint called if applicable" - }, - "result": { - "type": ["string", "null"], - "description": "Summary of action result" - }, - "metadata": { - "type": "object", - "default": {}, - "description": "Additional contextual data for this action" - } - }, - "required": ["timestamp", "action"], - "additionalProperties": false - }, - "default": [] - }, - "api_specs": { - "type": "object", - "patternProperties": { - "^[a-zA-Z0-9_-]+$": { - "type": "string", - "format": "uri" - } - }, - "description": "API specifications for enhanced responses", - "default": {}, - "additionalProperties": false - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "When this context was first created" - }, - "last_updated": { - "type": "string", - "format": "date-time", - "description": "When this context was last modified" - } - }, - "additionalProperties": false -} \ No newline at end of file diff --git a/src/ocp_agent/schemas/ocp-openapi-extensions.json b/src/ocp_agent/schemas/ocp-openapi-extensions.json deleted file mode 100644 index 263412c..0000000 --- a/src/ocp_agent/schemas/ocp-openapi-extensions.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://opencontextprotocol.org/schemas/ocp-openapi-extensions.json", - "title": "OCP Extensions for OpenAPI", - "description": "Optional OCP extensions for OpenAPI specifications to declare context support and behavior", - "type": "object", - "definitions": { - "ocpInfoExtensions": { - "type": "object", - "description": "OCP extensions for OpenAPI info section", - "properties": { - "x-ocp-enabled": { - "type": "boolean", - "description": "Indicates this API acknowledges OCP context headers", - "default": false - }, - "x-ocp-context-aware": { - "type": "boolean", - "description": "Indicates this API can read and utilize context data in responses", - "default": false - }, - "x-ocp-agent-types": { - "type": "array", - "description": "Agent types this API is optimized for", - "items": { - "type": "string" - } - }, - "x-ocp-version": { - "type": "string", - "description": "OCP specification version this API supports", - "pattern": "^[0-9]+\\.[0-9]+$" - } - } - }, - "ocpOperationExtensions": { - "type": "object", - "description": "OCP extensions for OpenAPI operation objects", - "properties": { - "x-ocp-context": { - "type": "object", - "description": "OCP context behavior for this operation", - "properties": { - "enhances_with": { - "type": "array", - "description": "Context fields this operation uses to enhance responses", - "items": { - "type": "string", - "enum": [ - "agent_goal", - "workspace_context", - "conversation_history", - "current_file", - "recent_changes", - "error_context", - "session" - ] - } - }, - "updates_context": { - "type": "array", - "description": "Context fields this operation may update", - "items": { - "type": "string", - "enum": [ - "current_goal", - "context_summary", - "error_context", - "recent_changes", - "history" - ] - } - }, - "requires_context": { - "type": "boolean", - "description": "Whether this operation requires OCP context to function properly", - "default": false - } - } - }, - "x-ocp-enhanced-response": { - "type": "object", - "description": "Additional response properties when OCP context is present", - "patternProperties": { - "^[a-zA-Z][a-zA-Z0-9_]*$": { - "type": "object", - "description": "OpenAPI schema for additional response property" - } - }, - "additionalProperties": false - } - } - }, - "ocpResponseExtensions": { - "type": "object", - "description": "OCP extensions for OpenAPI response objects", - "properties": { - "x-ocp-context-updated": { - "type": "boolean", - "description": "Indicates this response may include updated context in headers", - "default": false - } - } - } - } -} \ No newline at end of file diff --git a/src/ocp_agent/schemas/ocp-tool.json b/src/ocp_agent/schemas/ocp-tool.json deleted file mode 100644 index c97af5c..0000000 --- a/src/ocp_agent/schemas/ocp-tool.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://opencontextprotocol.org/schemas/ocp-tool.json", - "title": "OCP Tool Definition", - "description": "Tool generated from OpenAPI specification for OCP agent use", - "type": "object", - "required": ["name", "description", "method", "path", "parameters", "response_schema"], - "properties": { - "name": { - "type": "string", - "description": "Deterministic camelCase tool name generated from operationId or method+path", - "pattern": "^[a-z][a-zA-Z0-9]*$" - }, - "description": { - "type": "string", - "description": "Human-readable description from OpenAPI operation summary/description" - }, - "method": { - "type": "string", - "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"], - "description": "HTTP method for this operation" - }, - "path": { - "type": "string", - "description": "API endpoint path template with {parameter} placeholders", - "pattern": "^/" - }, - "operation_id": { - "type": ["string", "null"], - "description": "Original OpenAPI operationId if present" - }, - "parameters": { - "type": "object", - "description": "Parameter definitions extracted from OpenAPI specification", - "patternProperties": { - "^[a-zA-Z][a-zA-Z0-9_]*$": { - "type": "object", - "required": ["type", "required", "location"], - "properties": { - "type": { - "type": "string", - "enum": ["string", "number", "integer", "boolean", "array", "object"], - "description": "Parameter data type" - }, - "required": { - "type": "boolean", - "description": "Whether this parameter is required" - }, - "location": { - "type": "string", - "enum": ["path", "query", "header", "body"], - "description": "Where this parameter should be placed in the HTTP request" - }, - "schema": { - "type": "object", - "description": "Full OpenAPI schema definition for this parameter" - }, - "description": { - "type": "string", - "description": "Human-readable parameter description", - "default": "" - }, - "default": { - "description": "Default value for optional parameters" - }, - "enum": { - "type": "array", - "description": "Allowed values for enumerated parameters" - }, - "format": { - "type": "string", - "description": "OpenAPI format specifier (e.g., 'date-time', 'email')" - }, - "items": { - "type": "object", - "description": "Schema for array items when type is 'array'" - }, - "properties": { - "type": "object", - "description": "Property schemas when type is 'object'" - }, - "minimum": { - "type": "number", - "description": "Minimum value for numeric parameters" - }, - "maximum": { - "type": "number", - "description": "Maximum value for numeric parameters" - }, - "minLength": { - "type": "integer", - "description": "Minimum length for string parameters" - }, - "maxLength": { - "type": "integer", - "description": "Maximum length for string parameters" - }, - "pattern": { - "type": "string", - "description": "Regular expression pattern for string validation" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "response_schema": { - "type": "object", - "description": "OpenAPI response schema for successful responses (2xx status codes)", - "properties": { - "type": { - "type": "string", - "description": "Response data type" - }, - "properties": { - "type": "object", - "description": "Object property definitions" - }, - "items": { - "type": "object", - "description": "Array item schema definition" - } - }, - "additionalProperties": true - }, - "tags": { - "type": ["array", "null"], - "items": { - "type": "string" - }, - "description": "OpenAPI tags for categorizing and organizing tools", - "uniqueItems": true - }, - "security": { - "type": "array", - "description": "Security requirements from OpenAPI specification", - "items": { - "type": "object", - "additionalProperties": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "servers": { - "type": "array", - "description": "Server information from OpenAPI specification", - "items": { - "type": "object", - "required": ["url"], - "properties": { - "url": { - "type": "string", - "format": "uri", - "description": "Base URL for this server" - }, - "description": { - "type": "string", - "description": "Human-readable description of this server" - } - } - } - }, - "deprecated": { - "type": "boolean", - "description": "Whether this operation is marked as deprecated in OpenAPI", - "default": false - } - }, - "additionalProperties": false -} \ No newline at end of file From 3e6621ddcd88884bb4be7c9ccff0837f08f79084 Mon Sep 17 00:00:00 2001 From: Nathan Allen Date: Sat, 7 Mar 2026 11:18:22 -0500 Subject: [PATCH 3/6] Braid: Add mirror 'src/ocp_agent/schemas' at '86c82b4' --- .braids.json | 11 ++ src/ocp_agent/schemas/ocp-context.json | 138 ++++++++++++++ .../schemas/ocp-openapi-extensions.json | 106 +++++++++++ src/ocp_agent/schemas/ocp-tool.json | 174 ++++++++++++++++++ 4 files changed, 429 insertions(+) create mode 100644 .braids.json create mode 100644 src/ocp_agent/schemas/ocp-context.json create mode 100644 src/ocp_agent/schemas/ocp-openapi-extensions.json create mode 100644 src/ocp_agent/schemas/ocp-tool.json diff --git a/.braids.json b/.braids.json new file mode 100644 index 0000000..dd8f6e8 --- /dev/null +++ b/.braids.json @@ -0,0 +1,11 @@ +{ + "config_version": 1, + "mirrors": { + "src/ocp_agent/schemas": { + "url": "https://github.com/opencontextprotocol/ocp-spec.git", + "branch": "main", + "path": "schemas", + "revision": "86c82b4bdf319480d0a11ceac699e6c8a0f94a2b" + } + } +} diff --git a/src/ocp_agent/schemas/ocp-context.json b/src/ocp_agent/schemas/ocp-context.json new file mode 100644 index 0000000..22df43d --- /dev/null +++ b/src/ocp_agent/schemas/ocp-context.json @@ -0,0 +1,138 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://opencontextprotocol.org/schemas/ocp-context.json", + "title": "OCP Context Object", + "description": "Agent context for Open Context Protocol", + "type": "object", + "required": [ + "context_id", + "agent_type", + "created_at", + "last_updated" + ], + "properties": { + "context_id": { + "type": "string", + "pattern": "^ocp-[a-f0-9]{8,}$", + "description": "Unique identifier for this context" + }, + "agent_type": { + "type": "string", + "description": "Type of agent creating this context" + }, + "user": { + "type": ["string", "null"], + "description": "User identifier" + }, + "workspace": { + "type": ["string", "null"], + "description": "Current workspace or project" + }, + "current_file": { + "type": ["string", "null"], + "description": "Currently active file" + }, + "current_goal": { + "type": ["string", "null"], + "description": "Agent's current objective" + }, + "context_summary": { + "type": ["string", "null"], + "description": "Brief summary of conversation context" + }, + "error_context": { + "type": ["string", "null"], + "description": "Error information for debugging" + }, + "recent_changes": { + "type": "array", + "items": { + "type": "string" + }, + "maxItems": 10, + "description": "Recent changes or modifications (max 10)", + "default": [] + }, + "session": { + "type": "object", + "description": "Session tracking information for this context", + "properties": { + "start_time": { + "type": "string", + "format": "date-time", + "description": "Session start timestamp" + }, + "interaction_count": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "Number of interactions in this session" + }, + "agent_type": { + "type": "string", + "description": "Type of agent for this session" + } + }, + "required": ["start_time", "interaction_count", "agent_type"], + "additionalProperties": true, + "default": {} + }, + "history": { + "type": "array", + "description": "Chronological record of actions and API calls", + "items": { + "type": "object", + "properties": { + "timestamp": { + "type": "string", + "format": "date-time", + "description": "When this action occurred" + }, + "action": { + "type": "string", + "description": "Type of action performed" + }, + "api_endpoint": { + "type": ["string", "null"], + "description": "API endpoint called if applicable" + }, + "result": { + "type": ["string", "null"], + "description": "Summary of action result" + }, + "metadata": { + "type": "object", + "default": {}, + "description": "Additional contextual data for this action" + } + }, + "required": ["timestamp", "action"], + "additionalProperties": false + }, + "default": [] + }, + "api_specs": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "string", + "format": "uri" + } + }, + "description": "API specifications for enhanced responses", + "default": {}, + "additionalProperties": false + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "When this context was first created" + }, + "last_updated": { + "type": "string", + "format": "date-time", + "description": "When this context was last modified" + } + }, + "additionalProperties": false +} \ No newline at end of file diff --git a/src/ocp_agent/schemas/ocp-openapi-extensions.json b/src/ocp_agent/schemas/ocp-openapi-extensions.json new file mode 100644 index 0000000..263412c --- /dev/null +++ b/src/ocp_agent/schemas/ocp-openapi-extensions.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://opencontextprotocol.org/schemas/ocp-openapi-extensions.json", + "title": "OCP Extensions for OpenAPI", + "description": "Optional OCP extensions for OpenAPI specifications to declare context support and behavior", + "type": "object", + "definitions": { + "ocpInfoExtensions": { + "type": "object", + "description": "OCP extensions for OpenAPI info section", + "properties": { + "x-ocp-enabled": { + "type": "boolean", + "description": "Indicates this API acknowledges OCP context headers", + "default": false + }, + "x-ocp-context-aware": { + "type": "boolean", + "description": "Indicates this API can read and utilize context data in responses", + "default": false + }, + "x-ocp-agent-types": { + "type": "array", + "description": "Agent types this API is optimized for", + "items": { + "type": "string" + } + }, + "x-ocp-version": { + "type": "string", + "description": "OCP specification version this API supports", + "pattern": "^[0-9]+\\.[0-9]+$" + } + } + }, + "ocpOperationExtensions": { + "type": "object", + "description": "OCP extensions for OpenAPI operation objects", + "properties": { + "x-ocp-context": { + "type": "object", + "description": "OCP context behavior for this operation", + "properties": { + "enhances_with": { + "type": "array", + "description": "Context fields this operation uses to enhance responses", + "items": { + "type": "string", + "enum": [ + "agent_goal", + "workspace_context", + "conversation_history", + "current_file", + "recent_changes", + "error_context", + "session" + ] + } + }, + "updates_context": { + "type": "array", + "description": "Context fields this operation may update", + "items": { + "type": "string", + "enum": [ + "current_goal", + "context_summary", + "error_context", + "recent_changes", + "history" + ] + } + }, + "requires_context": { + "type": "boolean", + "description": "Whether this operation requires OCP context to function properly", + "default": false + } + } + }, + "x-ocp-enhanced-response": { + "type": "object", + "description": "Additional response properties when OCP context is present", + "patternProperties": { + "^[a-zA-Z][a-zA-Z0-9_]*$": { + "type": "object", + "description": "OpenAPI schema for additional response property" + } + }, + "additionalProperties": false + } + } + }, + "ocpResponseExtensions": { + "type": "object", + "description": "OCP extensions for OpenAPI response objects", + "properties": { + "x-ocp-context-updated": { + "type": "boolean", + "description": "Indicates this response may include updated context in headers", + "default": false + } + } + } + } +} \ No newline at end of file diff --git a/src/ocp_agent/schemas/ocp-tool.json b/src/ocp_agent/schemas/ocp-tool.json new file mode 100644 index 0000000..c97af5c --- /dev/null +++ b/src/ocp_agent/schemas/ocp-tool.json @@ -0,0 +1,174 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://opencontextprotocol.org/schemas/ocp-tool.json", + "title": "OCP Tool Definition", + "description": "Tool generated from OpenAPI specification for OCP agent use", + "type": "object", + "required": ["name", "description", "method", "path", "parameters", "response_schema"], + "properties": { + "name": { + "type": "string", + "description": "Deterministic camelCase tool name generated from operationId or method+path", + "pattern": "^[a-z][a-zA-Z0-9]*$" + }, + "description": { + "type": "string", + "description": "Human-readable description from OpenAPI operation summary/description" + }, + "method": { + "type": "string", + "enum": ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"], + "description": "HTTP method for this operation" + }, + "path": { + "type": "string", + "description": "API endpoint path template with {parameter} placeholders", + "pattern": "^/" + }, + "operation_id": { + "type": ["string", "null"], + "description": "Original OpenAPI operationId if present" + }, + "parameters": { + "type": "object", + "description": "Parameter definitions extracted from OpenAPI specification", + "patternProperties": { + "^[a-zA-Z][a-zA-Z0-9_]*$": { + "type": "object", + "required": ["type", "required", "location"], + "properties": { + "type": { + "type": "string", + "enum": ["string", "number", "integer", "boolean", "array", "object"], + "description": "Parameter data type" + }, + "required": { + "type": "boolean", + "description": "Whether this parameter is required" + }, + "location": { + "type": "string", + "enum": ["path", "query", "header", "body"], + "description": "Where this parameter should be placed in the HTTP request" + }, + "schema": { + "type": "object", + "description": "Full OpenAPI schema definition for this parameter" + }, + "description": { + "type": "string", + "description": "Human-readable parameter description", + "default": "" + }, + "default": { + "description": "Default value for optional parameters" + }, + "enum": { + "type": "array", + "description": "Allowed values for enumerated parameters" + }, + "format": { + "type": "string", + "description": "OpenAPI format specifier (e.g., 'date-time', 'email')" + }, + "items": { + "type": "object", + "description": "Schema for array items when type is 'array'" + }, + "properties": { + "type": "object", + "description": "Property schemas when type is 'object'" + }, + "minimum": { + "type": "number", + "description": "Minimum value for numeric parameters" + }, + "maximum": { + "type": "number", + "description": "Maximum value for numeric parameters" + }, + "minLength": { + "type": "integer", + "description": "Minimum length for string parameters" + }, + "maxLength": { + "type": "integer", + "description": "Maximum length for string parameters" + }, + "pattern": { + "type": "string", + "description": "Regular expression pattern for string validation" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "response_schema": { + "type": "object", + "description": "OpenAPI response schema for successful responses (2xx status codes)", + "properties": { + "type": { + "type": "string", + "description": "Response data type" + }, + "properties": { + "type": "object", + "description": "Object property definitions" + }, + "items": { + "type": "object", + "description": "Array item schema definition" + } + }, + "additionalProperties": true + }, + "tags": { + "type": ["array", "null"], + "items": { + "type": "string" + }, + "description": "OpenAPI tags for categorizing and organizing tools", + "uniqueItems": true + }, + "security": { + "type": "array", + "description": "Security requirements from OpenAPI specification", + "items": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "servers": { + "type": "array", + "description": "Server information from OpenAPI specification", + "items": { + "type": "object", + "required": ["url"], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "Base URL for this server" + }, + "description": { + "type": "string", + "description": "Human-readable description of this server" + } + } + } + }, + "deprecated": { + "type": "boolean", + "description": "Whether this operation is marked as deprecated in OpenAPI", + "default": false + } + }, + "additionalProperties": false +} \ No newline at end of file From 466c16d745685f379e28a2678561f7a3a595b5e4 Mon Sep 17 00:00:00 2001 From: Nathan Allen Date: Sat, 7 Mar 2026 11:19:02 -0500 Subject: [PATCH 4/6] Braid: Add mirror 'src/ocp_agent/dictionaries' at 'f322d11' --- .braids.json | 6 + src/ocp_agent/dictionaries/abbrs.json | 189 ++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 src/ocp_agent/dictionaries/abbrs.json diff --git a/.braids.json b/.braids.json index dd8f6e8..deca9aa 100644 --- a/.braids.json +++ b/.braids.json @@ -1,6 +1,12 @@ { "config_version": 1, "mirrors": { + "src/ocp_agent/dictionaries": { + "url": "https://github.com/opencontextprotocol/ocp-registry.git", + "branch": "main", + "path": "data/dictionaries", + "revision": "f322d11f8d1e6dd3d25fd49ae4f744baa4dc12fc" + }, "src/ocp_agent/schemas": { "url": "https://github.com/opencontextprotocol/ocp-spec.git", "branch": "main", diff --git a/src/ocp_agent/dictionaries/abbrs.json b/src/ocp_agent/dictionaries/abbrs.json new file mode 100644 index 0000000..6e871f2 --- /dev/null +++ b/src/ocp_agent/dictionaries/abbrs.json @@ -0,0 +1,189 @@ +{ + "version": "0.1.0", + "abbreviations": [ + { + "word": "repository", + "abbr": "repo" + }, + { + "word": "repositories", + "abbr": "repos" + }, + { + "word": "configuration", + "abbr": "config" + }, + { + "word": "application", + "abbr": "app" + }, + { + "word": "applications", + "abbr": "apps" + }, + { + "word": "authentication", + "abbr": "auth" + }, + { + "word": "authorization", + "abbr": "authz" + }, + { + "word": "administrator", + "abbr": "admin" + }, + { + "word": "administrators", + "abbr": "admins" + }, + { + "word": "environment", + "abbr": "env" + }, + { + "word": "environments", + "abbr": "envs" + }, + { + "word": "organization", + "abbr": "org" + }, + { + "word": "organizations", + "abbr": "orgs" + }, + { + "word": "information", + "abbr": "info" + }, + { + "word": "description", + "abbr": "desc" + }, + { + "word": "specification", + "abbr": "spec" + }, + { + "word": "specifications", + "abbr": "specs" + }, + { + "word": "parameter", + "abbr": "param" + }, + { + "word": "parameters", + "abbr": "params" + }, + { + "word": "argument", + "abbr": "arg" + }, + { + "word": "arguments", + "abbr": "args" + }, + { + "word": "reference", + "abbr": "ref" + }, + { + "word": "references", + "abbr": "refs" + }, + { + "word": "attribute", + "abbr": "attr" + }, + { + "word": "attributes", + "abbr": "attrs" + }, + { + "word": "statistic", + "abbr": "stat" + }, + { + "word": "statistics", + "abbr": "stats" + }, + { + "word": "document", + "abbr": "doc" + }, + { + "word": "documents", + "abbr": "docs" + }, + { + "word": "message", + "abbr": "msg" + }, + { + "word": "messages", + "abbr": "msgs" + }, + { + "word": "database", + "abbr": "db" + }, + { + "word": "databases", + "abbr": "dbs" + }, + { + "word": "temporary", + "abbr": "temp" + }, + { + "word": "maximum", + "abbr": "max" + }, + { + "word": "minimum", + "abbr": "min" + }, + { + "word": "kubernetes", + "abbr": "k8s" + }, + { + "word": "internationalization", + "abbr": "i18n" + }, + { + "word": "localization", + "abbr": "l10n" + }, + { + "word": "metadata", + "abbr": "meta" + }, + { + "word": "identifier", + "abbr": "id" + }, + { + "word": "identifiers", + "abbr": "ids" + }, + { + "word": "variable", + "abbr": "var" + }, + { + "word": "variables", + "abbr": "vars" + }, + { + "word": "package", + "abbr": "pkg" + }, + { + "word": "packages", + "abbr": "pkgs" + } + ] +} From 6c1d52c2e42fb5bccf1c693153fd7f2d3633d9d0 Mon Sep 17 00:00:00 2001 From: Nathan Allen Date: Fri, 13 Mar 2026 05:47:20 -0400 Subject: [PATCH 5/6] chore: bump pydantic-core to 2.42.0 --- poetry.lock | 480 ++++++++++++++++++++++++------------------------- pyproject.toml | 1 + 2 files changed, 237 insertions(+), 244 deletions(-) diff --git a/poetry.lock b/poetry.lock index 38b2e65..863db6b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -46,137 +46,137 @@ files = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c"}, - {file = "certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120"}, + {file = "certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa"}, + {file = "certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7"}, ] [[package]] name = "charset-normalizer" -version = "3.4.4" +version = "3.4.5" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d"}, - {file = "charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016"}, - {file = "charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525"}, - {file = "charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14"}, - {file = "charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c"}, - {file = "charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win32.whl", hash = "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa"}, - {file = "charset_normalizer-3.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966"}, - {file = "charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50"}, - {file = "charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f"}, - {file = "charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4167a621a9a1a986c73777dbc15d4b5eac8ac5c10393374109a343d4013ec765"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f64c6bf8f32f9133b668c7f7a7cbdbc453412bc95ecdbd157f3b1e377a92990"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:568e3c34b58422075a1b49575a6abc616d9751b4d61b23f712e12ebb78fe47b2"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:036c079aa08a6a592b82487f97c60b439428320ed1b2ea0b3912e99d30c77765"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:340810d34ef83af92148e96e3e44cb2d3f910d2bf95e5618a5c467d9f102231d"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:cd2d0f0ec9aa977a27731a3209ebbcacebebaf41f902bd453a928bfd281cf7f8"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b362bcd27819f9c07cbf23db4e0e8cd4b44c5ecd900c2ff907b2b92274a7412"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:77be992288f720306ab4108fe5c74797de327f3248368dfc7e1a916d6ed9e5a2"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:8b78d8a609a4b82c273257ee9d631ded7fac0d875bdcdccc109f3ee8328cfcb1"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ba20bdf69bd127f66d0174d6f2a93e69045e0b4036dc1ca78e091bcc765830c4"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:76a9d0de4d0eab387822e7b35d8f89367dd237c72e82ab42b9f7bf5e15ada00f"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8fff79bf5978c693c9b1a4d71e4a94fddfb5fe744eb062a318e15f4a2f63a550"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c7e84e0c0005e3bdc1a9211cd4e62c78ba80bc37b2365ef4410cd2007a9047f2"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-win32.whl", hash = "sha256:58ad8270cfa5d4bef1bc85bd387217e14ff154d6630e976c6f56f9a040757475"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-win_amd64.whl", hash = "sha256:02a9d1b01c1e12c27883b0c9349e0bcd9ae92e727ff1a277207e1a262b1cbf05"}, + {file = "charset_normalizer-3.4.5-cp310-cp310-win_arm64.whl", hash = "sha256:039215608ac7b358c4da0191d10fc76868567fbf276d54c14721bdedeb6de064"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:610f72c0ee565dfb8ae1241b666119582fdbfe7c0975c175be719f940e110694"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60d68e820af339df4ae8358c7a2e7596badeb61e544438e489035f9fbf3246a5"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b473fc8dca1c3ad8559985794815f06ca3fc71942c969129070f2c3cdf7281"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d4eb8ac7469b2a5d64b5b8c04f84d8bf3ad340f4514b98523805cbf46e3b3923"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bcb3227c3d9aaf73eaaab1db7ccd80a8995c509ee9941e2aae060ca6e4e5d81"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:75ee9c1cce2911581a70a3c0919d8bccf5b1cbc9b0e5171400ec736b4b569497"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1d1401945cb77787dbd3af2446ff2d75912327c4c3a1526ab7955ecf8600687c"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a45e504f5e1be0bd385935a8e1507c442349ca36f511a47057a71c9d1d6ea9e"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e09f671a54ce70b79a1fc1dc6da3072b7ef7251fadb894ed92d9aa8218465a5f"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d01de5e768328646e6a3fa9e562706f8f6641708c115c62588aef2b941a4f88e"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:131716d6786ad5e3dc542f5cc6f397ba3339dc0fb87f87ac30e550e8987756af"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a374cc0b88aa710e8865dc1bd6edb3743c59f27830f0293ab101e4cf3ce9f85"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d31f0d1671e1534e395f9eb84a68e0fb670e1edb1fe819a9d7f564ae3bc4e53f"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-win32.whl", hash = "sha256:cace89841c0599d736d3d74a27bc5821288bb47c5441923277afc6059d7fbcb4"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-win_amd64.whl", hash = "sha256:f8102ae93c0bc863b1d41ea0f4499c20a83229f52ed870850892df555187154a"}, + {file = "charset_normalizer-3.4.5-cp311-cp311-win_arm64.whl", hash = "sha256:ed98364e1c262cf5f9363c3eca8c2df37024f52a8fa1180a3610014f26eac51c"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed97c282ee4f994ef814042423a529df9497e3c666dca19be1d4cd1129dc7ade"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0294916d6ccf2d069727d65973c3a1ca477d68708db25fd758dd28b0827cff54"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dc57a0baa3eeedd99fafaef7511b5a6ef4581494e8168ee086031744e2679467"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ed1a9a204f317ef879b32f9af507d47e49cd5e7f8e8d5d96358c98373314fc60"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad83b8f9379176c841f8865884f3514d905bcd2a9a3b210eaa446e7d2223e4d"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:a118e2e0b5ae6b0120d5efa5f866e58f2bb826067a646431da4d6a2bdae7950e"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:754f96058e61a5e22e91483f823e07df16416ce76afa4ebf306f8e1d1296d43f"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c300cefd9b0970381a46394902cd18eaf2aa00163f999590ace991989dcd0fc"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c108f8619e504140569ee7de3f97d234f0fbae338a7f9f360455071ef9855a95"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d1028de43596a315e2720a9849ee79007ab742c06ad8b45a50db8cdb7ed4a82a"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:19092dde50335accf365cce21998a1c6dd8eafd42c7b226eb54b2747cdce2fac"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4354e401eb6dab9aed3c7b4030514328a6c748d05e1c3e19175008ca7de84fb1"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a68766a3c58fde7f9aaa22b3786276f62ab2f594efb02d0a1421b6282e852e98"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-win32.whl", hash = "sha256:1827734a5b308b65ac54e86a618de66f935a4f63a8a462ff1e19a6788d6c2262"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-win_amd64.whl", hash = "sha256:728c6a963dfab66ef865f49286e45239384249672cd598576765acc2a640a636"}, + {file = "charset_normalizer-3.4.5-cp312-cp312-win_arm64.whl", hash = "sha256:75dfd1afe0b1647449e852f4fb428195a7ed0588947218f7ba929f6538487f02"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ac59c15e3f1465f722607800c68713f9fbc2f672b9eb649fe831da4019ae9b23"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:165c7b21d19365464e8f70e5ce5e12524c58b48c78c1f5a57524603c1ab003f8"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:28269983f25a4da0425743d0d257a2d6921ea7d9b83599d4039486ec5b9f911d"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d27ce22ec453564770d29d03a9506d449efbb9fa13c00842262b2f6801c48cce"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0625665e4ebdddb553ab185de5db7054393af8879fb0c87bd5690d14379d6819"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:c23eb3263356d94858655b3e63f85ac5d50970c6e8febcdde7830209139cc37d"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e6302ca4ae283deb0af68d2fbf467474b8b6aedcd3dab4db187e07f94c109763"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e51ae7d81c825761d941962450f50d041db028b7278e7b08930b4541b3e45cb9"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:597d10dec876923e5c59e48dbd366e852eacb2b806029491d307daea6b917d7c"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5cffde4032a197bd3b42fd0b9509ec60fb70918d6970e4cc773f20fc9180ca67"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2da4eedcb6338e2321e831a0165759c0c620e37f8cd044a263ff67493be8ffb3"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:65a126fb4b070d05340a84fc709dd9e7c75d9b063b610ece8a60197a291d0adf"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7a80a9242963416bd81f99349d5f3fce1843c303bd404f204918b6d75a75fd6"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-win32.whl", hash = "sha256:f1d725b754e967e648046f00c4facc42d414840f5ccc670c5670f59f83693e4f"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-win_amd64.whl", hash = "sha256:e37bd100d2c5d3ba35db9c7c5ba5a9228cbcffe5c4778dc824b164e5257813d7"}, + {file = "charset_normalizer-3.4.5-cp313-cp313-win_arm64.whl", hash = "sha256:93b3b2cc5cf1b8743660ce77a4f45f3f6d1172068207c1defc779a36eea6bb36"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8197abe5ca1ffb7d91e78360f915eef5addff270f8a71c1fc5be24a56f3e4873"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2aecdb364b8a1802afdc7f9327d55dad5366bc97d8502d0f5854e50712dbc5f"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a66aa5022bf81ab4b1bebfb009db4fd68e0c6d4307a1ce5ef6a26e5878dfc9e4"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d77f97e515688bd615c1d1f795d540f32542d514242067adcb8ef532504cb9ee"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01a1ed54b953303ca7e310fafe0fe347aab348bd81834a0bcd602eb538f89d66"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:b2d37d78297b39a9eb9eb92c0f6df98c706467282055419df141389b23f93362"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e71bbb595973622b817c042bd943c3f3667e9c9983ce3d205f973f486fec98a7"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4cd966c2559f501c6fd69294d082c2934c8dd4719deb32c22961a5ac6db0df1d"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d5e52d127045d6ae01a1e821acfad2f3a1866c54d0e837828538fabe8d9d1bd6"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:30a2b1a48478c3428d047ed9690d57c23038dac838a87ad624c85c0a78ebeb39"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d8ed79b8f6372ca4254955005830fd61c1ccdd8c0fac6603e2c145c61dd95db6"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:c5af897b45fa606b12464ccbe0014bbf8c09191e0a66aab6aa9d5cf6e77e0c94"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1088345bcc93c58d8d8f3d783eca4a6e7a7752bbff26c3eee7e73c597c191c2e"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-win32.whl", hash = "sha256:ee57b926940ba00bca7ba7041e665cc956e55ef482f851b9b65acb20d867e7a2"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-win_amd64.whl", hash = "sha256:4481e6da1830c8a1cc0b746b47f603b653dadb690bcd851d039ffaefe70533aa"}, + {file = "charset_normalizer-3.4.5-cp314-cp314-win_arm64.whl", hash = "sha256:97ab7787092eb9b50fb47fa04f24c75b768a606af1bcba1957f07f128a7219e4"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e22d1059b951e7ae7c20ef6b06afd10fb95e3c41bf3c4fbc874dba113321c193"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afca7f78067dd27c2b848f1b234623d26b87529296c6c5652168cc1954f2f3b2"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ec56a2266f32bc06ed3c3e2a8f58417ce02f7e0356edc89786e52db13c593c98"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b970382e4a36bed897c19f310f31d7d13489c11b4f468ddfba42d41cddfb918"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:573ef5814c4b7c0d59a7710aa920eaaaef383bd71626aa420fba27b5cab92e8d"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-manylinux_2_31_armv7l.whl", hash = "sha256:50bcbca6603c06a1dcc7b056ed45c37715fb5d2768feb3bcd37d2313c587a5b9"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1f2da5cbb9becfcd607757a169e38fb82aa5fd86fae6653dea716e7b613fe2cf"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc1c64934b8faf7584924143eb9db4770bbdb16659626e1a1a4d9efbcb68d947"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:ae8b03427410731469c4033934cf473426faff3e04b69d2dfb64a4281a3719f8"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:b3e71afc578b98512bfe7bdb822dd6bc57d4b0093b4b6e5487c1e96ad4ace242"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:4b8551b6e6531e156db71193771c93bda78ffc4d1e6372517fe58ad3b91e4659"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:65b3c403a5b6b8034b655e7385de4f72b7b244869a22b32d4030b99a60593eca"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8ce11cd4d62d11166f2b441e30ace226c19a3899a7cf0796f668fba49a9fb123"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-win32.whl", hash = "sha256:66dee73039277eb35380d1b82cccc69cc82b13a66f9f4a18da32d573acf02b7c"}, + {file = "charset_normalizer-3.4.5-cp38-cp38-win_amd64.whl", hash = "sha256:d29dd9c016f2078b43d0c357511e87eee5b05108f3dd603423cb389b89813969"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:259cd1ca995ad525f638e131dbcc2353a586564c038fc548a3fe450a91882139"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a28afb04baa55abf26df544e3e5c6534245d3daa5178bc4a8eeb48202060d0e"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ff95a9283de8a457e6b12989de3f9f5193430f375d64297d323a615ea52cbdb3"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:708c7acde173eedd4bfa4028484426ba689d2103b28588c513b9db2cd5ecde9c"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa92ec1102eaff840ccd1021478af176a831f1bccb08e526ce844b7ddda85c22"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:5fea359734b140d0d6741189fea5478c6091b54ffc69d7ce119e0a05637d8c99"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e545b51da9f9af5c67815ca0eb40676c0f016d0b0381c86f20451e35696c5f95"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:30987f4a8ed169983f93e1be8ffeea5214a779e27ed0b059835c7afe96550ad7"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:149ec69866c3d6c2fb6f758dbc014ecb09f30b35a5ca90b6a8a2d4e54e18fdfe"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:530beedcec9b6e027e7a4b6ce26eed36678aa39e17da85e6e03d7bd9e8e9d7c9"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:14498a429321de554b140013142abe7608f9d8ccc04d7baf2ad60498374aefa2"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2820a98460c83663dd8ec015d9ddfd1e4879f12e06bb7d0500f044fb477d2770"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:aa2f963b4da26daf46231d9b9e0e2c9408a751f8f0d0f44d2de56d3caf51d294"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-win32.whl", hash = "sha256:82cc7c2ad42faec8b574351f8bc2a0c049043893853317bd9bb309f5aba6cb5a"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-win_amd64.whl", hash = "sha256:92263f7eca2f4af326cd20de8d16728d2602f7cfea02e790dcde9d83c365d7cc"}, + {file = "charset_normalizer-3.4.5-cp39-cp39-win_arm64.whl", hash = "sha256:014837af6fabf57121b6254fa8ade10dceabc3528b27b721a64bbc7b8b1d4eb4"}, + {file = "charset_normalizer-3.4.5-py3-none-any.whl", hash = "sha256:9db5e3fcdcee89a78c04dffb3fe33c79f77bd741a624946db2591c81b2fc85b0"}, + {file = "charset_normalizer-3.4.5.tar.gz", hash = "sha256:95adae7b6c42a6c5b5b559b1a99149f090a57128155daeea91732c8d970d8644"}, ] [[package]] @@ -486,19 +486,19 @@ testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "pydantic" -version = "2.12.5" +version = "2.13.0b2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, - {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, + {file = "pydantic-2.13.0b2-py3-none-any.whl", hash = "sha256:42a3dee97ad2b50b7489ad4fe8dfec509cb613487da9a3c19d480f0880e223bc"}, + {file = "pydantic-2.13.0b2.tar.gz", hash = "sha256:255b95518090cd7090b605ef975957b07f724778f71dafc850a7442e088e7b99"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.41.5" +pydantic-core = "2.42.0" typing-extensions = ">=4.14.1" typing-inspection = ">=0.4.2" @@ -508,133 +508,125 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.41.5" +version = "2.42.0" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, - {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, - {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, - {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, - {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, - {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, - {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, - {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, - {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, - {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, - {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, - {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, - {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, - {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, - {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, - {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, - {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, - {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, - {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, - {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, - {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, - {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, - {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, - {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, - {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, - {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, - {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, - {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, + {file = "pydantic_core-2.42.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:0ae7d50a47ada2a04f7296be9a7a2bf447118a25855f41fc52c8fc4bfb70c105"}, + {file = "pydantic_core-2.42.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c9d04d4bd8de1dcd5c8845faf6c11e36cda34c2efffa29d70ad83cc6f6a6c9a8"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e459e89453bb1bc69853272260afb5328ae404f854ddec485f5427fbace8d7e"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:def66968fbe20274093fd4fc85d82b2ec42dbe20d9e51d27bbf3b5c7428c7a10"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:272fab515dc7da0f456c49747b87b4e8721a33ab352a54760cc8fd1a4fd5348a"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa82dec59f36106738ae981878e0001074e2b3a949f21a5b3bea20485b9c6db4"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a70fe4db00ab03a9f976d28471c8e696ebd3b8455ccfa5e36e5d1a2ff301a7"}, + {file = "pydantic_core-2.42.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b4c0f656b4fa218413a485c550ac3e4ddf2f343a9c46b6137394bd77c4128445"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a4396ffc8b42499d14662f958b3f00656b62a67bde7f156580fd618827bebf5a"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:36067825f365a5c3065f17d08421a72b036ff4588c450afe54d5750b80cc220d"}, + {file = "pydantic_core-2.42.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eec64367de940786c0b686d47bd952692018dd7cd895027aa82023186e469b7d"}, + {file = "pydantic_core-2.42.0-cp310-cp310-win32.whl", hash = "sha256:ff9f0737f487277721682d8518434557cfcef141ba55b89381c92700594a8b65"}, + {file = "pydantic_core-2.42.0-cp310-cp310-win_amd64.whl", hash = "sha256:77f0a8ab035d3bc319b759d8215f51846e9ea582dacbabb2777e5e3e135a048e"}, + {file = "pydantic_core-2.42.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a1159b9ee73511ae7c5631b108d80373577bc14f22d18d85bb2aa1fa1051dabc"}, + {file = "pydantic_core-2.42.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff8e49b22225445d3e078aaa9bead90c37c852aee8f8a169ba15fdaaa13d1ecb"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe777d9a1a932c6b3ef32b201985324d06d9c74028adef1e1c7ea226fca2ba34"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e92592c1040ed17968d603e05b72acec321662ef9bf88fef443ceae4d1a130c2"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:557a6eb6dc4db8a3f071929710feb29c6b5d7559218ab547a4e60577fb404f2f"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4035f81e7d1a5e065543061376ca52ccb0accaf970911ba0a9ec9d22062806ca"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63a4e073f8def1c7fd100a355b3a96e1bbaf0446b6a8530ae58f1afaa0478a46"}, + {file = "pydantic_core-2.42.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dd8469c8d9f6c81befd10c72a0268079e929ba494cd27fa63e868964b0e04fb6"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bdebfd610a02bdb82f8e36dc7d4683e03e420624a2eda63e1205730970021308"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:9577eb5221abd4e5adf8a232a65f74c509b82b57b7b96b3667dac22f03ff9e94"}, + {file = "pydantic_core-2.42.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6d36841b61100128c2374341a7c2c0ab347ef4b63aa4b6837b4431465d4d4fd"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win32.whl", hash = "sha256:1d9d45333a28b0b8fb8ecedf67d280dc3318899988093e4d3a81618396270697"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win_amd64.whl", hash = "sha256:4631b4d1a3fe460aadd3822af032bb6c2e7ad77071fbf71c4e95ef9083c7c1a8"}, + {file = "pydantic_core-2.42.0-cp311-cp311-win_arm64.whl", hash = "sha256:3d46bfc6175a4b4b80b9f98f76133fbf68d5a02d7469b3090ca922d40f23d32d"}, + {file = "pydantic_core-2.42.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a11b9115364681779bcc39c6b9cdc20d48a9812a4bf3ed986fec4f694ed3a1e7"}, + {file = "pydantic_core-2.42.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c43088e8a44ccb2a2329d83892110587ebe661090b546dd03624a933fc4cfd0d"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13a7f9dde97c8400de559b2b2dcd9439f7b2b8951dad9b19711ef8c6e3f68ac0"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6380214c627f702993ea6b65b6aa8afc0f1481a179cdd169a2fc80a195e21158"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:606f80d8c61d4680ff82a34e9c49b7ab069b544b93393cc3c5906ac9e8eec7c9"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ab80ae93cb739de6c9ccc06a12cd731b079e1b25b03e2dcdccbc914389cc7e0"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:638f04b55bea04ec5bbda57a4743a51051f24b884abcb155b0ed2c3cb59ba448"}, + {file = "pydantic_core-2.42.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec72ba5c7555f69757b64b398509c7079fb22da705a6c67ac613e3f14a05f729"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e0364f6cd61be57bcd629c34788c197db211e91ce1c3009bf4bf97f6bb0eb21f"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:856f0fd81173b308cd6ceb714332cd9ea3c66ce43176c7defaed6b2ed51d745c"}, + {file = "pydantic_core-2.42.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1be705396e480ea96fd3cccd7512affda86823b8a2a8c196d9028ec37cb1ca77"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win32.whl", hash = "sha256:acacf0795d68e42d01ae8cc77ae19a5b3c80593e0fd60e4e2d336ec13d3de906"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win_amd64.whl", hash = "sha256:475a1a5ecf3a748a0d066b56138d258018c8145873ee899745c9f0e0af1cc4d4"}, + {file = "pydantic_core-2.42.0-cp312-cp312-win_arm64.whl", hash = "sha256:e2369cef245dd5aeafe6964cf43d571fb478f317251749c152c0ae564127053a"}, + {file = "pydantic_core-2.42.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:02fd2b4a62efa12e004fce2bfd2648cf8c39efc5dfc5ed5f196eb4ccefc7db4e"}, + {file = "pydantic_core-2.42.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c042694870c20053b8814a57c416cd2c6273fe462a440460005c791c24c39baf"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f905f3a082e7498dfaa70c204b236e92d448ba966ad112a96fcaaba2c4984fba"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4762081e8acc5458bf907373817cf93c927d451a1b294c1d0535b0570890d939"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4a433bbf6304bd114b96b0ce3ed9add2ee686df448892253bca5f622c030f31"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd695305724cfce8b19a18e87809c518f56905e5c03a19e3ad061974970f717d"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5f352ffa0ec2983b849a93714571063bfc57413b5df2f1027d7a04b6e8bdd25"}, + {file = "pydantic_core-2.42.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e61f2a194291338d76307a29e4881a8007542150b750900c1217117fc9bb698e"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:032f990dc1759f11f6b287e5c6eb1b0bcfbc18141779414a77269b420360b3bf"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:9c28b42768da6b9238554ae23b39291c3bbe6f53c4810aea6414d83efd59b96a"}, + {file = "pydantic_core-2.42.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:b22af1ac75fa873d81a65cce22ada1d840583b73a129b06133097c81f6f9e53b"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win32.whl", hash = "sha256:1de0350645c8643003176659ee70b637cd80e8514a063fff36f088fcda2dba06"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win_amd64.whl", hash = "sha256:d34b481a8a3eba3678a96e166c6e547c0c8b026844c13d9deb70c9f1fd2b0979"}, + {file = "pydantic_core-2.42.0-cp313-cp313-win_arm64.whl", hash = "sha256:5e0a65358eef041d95eef93fcf8834c2c8b83cc5a92d32f84bb3a7955dfe21c9"}, + {file = "pydantic_core-2.42.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:de4c9ad4615983b3fb2ee57f5c570cf964bda13353c6c41a54dac394927f0e54"}, + {file = "pydantic_core-2.42.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:129d5e6357814e4567e18b2ded4c210919aafd9ef0887235561f8d853fd34123"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c45582a5dac4649e512840ad212a5c2f9d168622f8db8863e8a29b54a29dfd"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a97fc19afb730b45de55d2e80093f1a36effc29538dec817204c929add8f2b4a"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45d83d38d94f22ffe9a0f0393b23e25bfefe4804ae63c8013906b76ab8de8ed"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3060192d8b63611a2abb26eccadddff5602a66491b8fafd9ae34fb67302ae84"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f17739150af9dc58b5c8fc3c4a1826ff84461f11b9f8ad5618445fcdd1ccec6"}, + {file = "pydantic_core-2.42.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d14e4c229467a7c27aa7c71e21584b3d77352ccb64e968fdbed4633373f73f7"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:aaef75e1b54366c7ccfbf4fc949ceaaa0f4c87e106df850354be6c7d45143db0"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:d2e362dceeeb4d56fd63e649c2de3ad4c3aa448b13ab8a9976e23a669f9c1854"}, + {file = "pydantic_core-2.42.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:a8edee724b527818bf0a6c8e677549794c0d0caffd14492851bd7a4ceab0f258"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win32.whl", hash = "sha256:a10c105c221f68221cb81be71f063111172f5ddf8b06f6494560e826c148f872"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win_amd64.whl", hash = "sha256:232d86e00870aceee7251aa5f4ab17e3e4864a4656c015f8e03d1223bf8e17ba"}, + {file = "pydantic_core-2.42.0-cp314-cp314-win_arm64.whl", hash = "sha256:9a6fce4e778c2fe2b3f1df63bfaa522c147668517ba040c49ad7f67a66867cff"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f4d1670fbc5488cfb18dd9fc71a2c7c8e12caeeb6e5bb641aa351ac5e01963cf"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:baeae16666139d0110f1006a06809228f5293ab84e77f4b9dda2bdee95d6c4e8"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a77c7a8cedf5557a4e5547dabf55a8ec99949162bd7925b312f6ec37c24101c"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:133fccf13546ff2a0610cc5b978dd4ee2c7f55a7a86b6b722fd6e857694bacc5"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad5dbebfbab92cf0f6d0b13d55bf0a239880a1534377edf6387e2e7a4469f131"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e6c0181016cb29ba4824940246606a8e13b1135de8306e00b5bd9d1efbc4cf85"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:020cfd7041cb71eac4dc93a29a6d5ec34f10b1fdc37f4f189c25bcc6748a2f97"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f73c6de3ee24f2b614d344491eda5628c4cdf3e7b79c0ac69bb40884ced2d319"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b2b448da50e1e8d5aac786dcf441afa761d26f1be4532b52cdf50864b47bd784"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:0df0488b1f548ef874b45bbc60a70631eee0177b79b5527344d7a253e77a5ed2"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:b8aa32697701dc36c956f4a78172549adbe25eacba952bbfbde786fb66316151"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win32.whl", hash = "sha256:173de56229897ff81b650ca9ed6f4c62401c49565234d3e9ae251119f6fd45c6"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2db227cf6797c286361f8d1e52b513f358a3ff9ebdede335e55a5edf4c59f06b"}, + {file = "pydantic_core-2.42.0-cp314-cp314t-win_arm64.whl", hash = "sha256:a983862733ecaf0b5c7275145f86397bde4ee1ad84cf650e1d7af7febe5f7073"}, + {file = "pydantic_core-2.42.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fc0834a2d658189c89d7a009ae19462da1d70fc4786d2b8e5c8c6971f4d3bcc1"}, + {file = "pydantic_core-2.42.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff69cf1eb517600d40c903dbc3507360e0a6c1ffa2dcf3cfa49a1c6fe203a46a"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3eab236da1c53a8cdf741765e31190906eb2838837bfedcaa6c0206b8f5975e"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15df82e324fa5b2b1403d5eb1bb186d14214c3ce0aebc9a3594435b82154d402"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ee7047297892d4fec68658898b7495be8c1a8a2932774e2d6810c3de1173783"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aec13272d859be1dd3344b75aab4d1d6690bfef78bd241628f6903c2bf101f8d"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e7adfd7794da8ae101d2d5e6a7be7cb39bb90d45b6aa42ecb502a256e94f8e0"}, + {file = "pydantic_core-2.42.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e3cfcacb42193479ead3aaba26a79e7df4c1c2415aefc43f1a60b57f50f8aa4"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cf89cee72f88db54763f800d32948bd6b1b9bf03e0ecb0a9cb93eac513caec5f"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c6ae4c08e6c4b08e35eb2b114803d09c5012602983d8bbd3564013d555dfe5fd"}, + {file = "pydantic_core-2.42.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:dfedd24ce01a3ea32f29c257e5a7fc79ed635cff0bd1a1aed12a22d3440cb39f"}, + {file = "pydantic_core-2.42.0-cp39-cp39-win32.whl", hash = "sha256:26ab24eecdec230bdf7ec519b9cd0c65348ec6e97304e87f9d3409749ea3377b"}, + {file = "pydantic_core-2.42.0-cp39-cp39-win_amd64.whl", hash = "sha256:f93228d630913af3bc2d55a50a96e0d33446b219aea9591bfdc0a06677f689ff"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:53ab90bed3a191750a6726fe2570606a9794608696063823d2deea734c100bf6"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:b8d9911a3cdb8062f4102499b666303c9a976202b420200a26606eafa0bfecf8"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe6b7b22dd1d326a1ab23b9e611a69c41d606cb723839755bb00456ebff3f672"}, + {file = "pydantic_core-2.42.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5e36849ca8e2e39828a70f1a86aa2b86f645a1d710223b6653f2fa8a130b703"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4d7e36c2a1f3c0020742190714388884a11282a0179f3d1c55796ee26b32dba5"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:41a702c2ac3dbbafa7d13bea142b3e04c8676d1fca199bac52b5ee24e6cdb737"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad5cb8ed96ffac804a0298f5d03f002769514700d79cbe77b66a27a6e605a65a"}, + {file = "pydantic_core-2.42.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51e33cf940cddcad333f85e15a25a2a949ac0a7f26fe8f43dc2d6816ce974ec4"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:495e70705f553c3b8f939965fa7cf77825c81417ff3c7ac046be9509b94c292c"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8757702cc696d48f9fdcb65cb835ca18bda5d83169fe6d13efd706e4195aea81"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32cc3087f38e4a9ee679f6184670a1b6591b8c3840c483f3342e176e215194d1"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e824d8f372aa717eeb435ee220c8247e514283a4fc0ecdc4ce44c09ee485a5b8"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e5900b257abb20371135f28b686d6990202dcdd9b7d8ff2e2290568aa0058280"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:f6705c73ab2abaebef81cad882a75afd6b8a0550e853768933610dce2945705e"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5ed95136324ceef6f33bd96ee3a299d36169175401204590037983aeb5bc73de"}, + {file = "pydantic_core-2.42.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9d729a3934e0ef3bc171025f0414d422aa6397d6bbd8176d5402739140e50616"}, + {file = "pydantic_core-2.42.0.tar.gz", hash = "sha256:34068adadf673c872f01265fa17ec00073e99d7f53f6d499bdfae652f330b3d2"}, ] [package.dependencies] @@ -1071,4 +1063,4 @@ zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "12fb023b41a3fd5096a632acb6952aa2c1be914032de9d305727a9a20d1b6d59" +content-hash = "2b262516d868341adf099a471a679d0f150d2dd06dae457e14989bb303a58211" diff --git a/pyproject.toml b/pyproject.toml index 658634f..a663a06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ httpx = "^0.28.0" jsonschema = "^4.26.0" pyyaml = "^6.0.0" pydantic = "^2.12.0" +pydantic-core = "^2.42.0" [tool.poetry.group.dev.dependencies] pytest = "^9.0" From 9897da7666097bc1d9ba2f43fa7fd6e89935f5f7 Mon Sep 17 00:00:00 2001 From: Nathan Allen Date: Sun, 15 Mar 2026 09:48:15 -0400 Subject: [PATCH 6/6] refactor: handle tool creation errors in _parse_openapi_spec --- src/ocp_agent/parsers/openapi_parser.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/ocp_agent/parsers/openapi_parser.py b/src/ocp_agent/parsers/openapi_parser.py index e40e870..071375b 100644 --- a/src/ocp_agent/parsers/openapi_parser.py +++ b/src/ocp_agent/parsers/openapi_parser.py @@ -136,11 +136,13 @@ def _parse_openapi_spec( for path, path_item in paths.items(): for method, operation in path_item.items(): if method.lower() in SUPPORTED_HTTP_METHODS: - tool = self._create_tool_from_operation( - path, method.upper(), operation, spec_data, memo_cache, path_prefix - ) - if tool: + try: + tool = self._create_tool_from_operation( + path, method.upper(), operation, spec_data, memo_cache, path_prefix + ) tools.append(tool) + except ValueError as e: + logger.debug(f"Skipping {method.upper()} {path}: {e}") return OCPAPISpec( base_url=base_url, @@ -178,15 +180,11 @@ def _create_tool_from_operation( spec_data: Dict[str, Any], memo_cache: Dict[str, Any], path_prefix: Optional[str] = None - ) -> Optional[OCPTool]: + ) -> OCPTool: """Create OCP tool from OpenAPI operation""" # Generate tool name using naming engine - try: - tool_name = self.naming_engine.generate_tool_name(path, method, path_prefix) - except Exception as e: - logger.warning(f"Skipping operation {method} {path}: failed to generate tool name: {e}") - return None + tool_name = self.naming_engine.generate_tool_name(path, method, path_prefix) # Store operation ID for reference operation_id = operation.get('operationId')