From 0a5a3e8011a0b06256a0dd0e9b6846fdfdde757c Mon Sep 17 00:00:00 2001 From: Joshua Napoli Date: Fri, 14 Nov 2025 16:48:07 -0500 Subject: [PATCH 1/6] [PD1-242] Update Ubuntu Version and [project] table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update GitHub Actions to use ubuntu-24.04 runner. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 642db7a..007eff2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] From 7adef73d45c7c457cc32f5d326b1137913028df8 Mon Sep 17 00:00:00 2001 From: Joshua Napoli Date: Thu, 20 Nov 2025 14:26:12 -0500 Subject: [PATCH 2/6] refactor: replace requests dependency with urllib MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the requests library with Python's built-in urllib to eliminate external HTTP dependency. This change uses urllib.request, urllib.error, and urllib.parse for all HTTP operations while maintaining the same functionality. Changes: - Updated CVec class to use urllib instead of requests - Modified test mocks to work with urllib - Updated error handling in csv_import script - Removed requests and types-requests from dependencies All tests pass successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- poetry.lock | 186 +----------------------------------- pyproject.toml | 2 - scripts/csv_import.py | 7 +- src/cvec/cvec.py | 137 +++++++++++++++----------- tests/test_token_refresh.py | 79 ++++++++------- 5 files changed, 130 insertions(+), 281 deletions(-) diff --git a/poetry.lock b/poetry.lock index 6c3963d..2b500cd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,120 +12,6 @@ files = [ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, ] -[[package]] -name = "certifi" -version = "2025.6.15" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, - {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, - {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, - {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, - {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, - {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, - {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, - {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, - {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, - {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, - {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, -] - [[package]] name = "colorama" version = "0.4.6" @@ -158,21 +44,6 @@ typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - [[package]] name = "iniconfig" version = "2.1.0" @@ -520,28 +391,6 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] -[[package]] -name = "requests" -version = "2.32.4" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - [[package]] name = "ruff" version = "0.11.13" @@ -613,21 +462,6 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] -[[package]] -name = "types-requests" -version = "2.32.4.20250913" -description = "Typing stubs for requests" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1"}, - {file = "types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d"}, -] - -[package.dependencies] -urllib3 = ">=2" - [[package]] name = "typing-extensions" version = "4.14.0" @@ -655,25 +489,7 @@ files = [ [package.dependencies] typing-extensions = ">=4.12.0" -[[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - [metadata] lock-version = "2.1" python-versions = ">=3.10" -content-hash = "8dbdd73c071b243cdffd7f2f8117e01aeb55f4cc4b19ee5b009c24668e6aa2b1" +content-hash = "466cefb197be201d5ccff4e90dd0c8844b48e58f580817e46a5ffbf6849eecea" diff --git a/pyproject.toml b/pyproject.toml index ca3d53e..47d8a09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,6 @@ authors = [{ name = "CVector", email = "support@cvector.energy" }] readme = "README.md" requires-python = ">=3.10" dependencies = [ - "requests (>=2.32.3,<3.0.0)", "pydantic>=2.7.0,<3.0.0", "pyarrow>=18.0.0,<20.0.0", ] @@ -19,7 +18,6 @@ packages = [{ include = "cvec", from = "src" }] pytest = "^8.3.5" mypy = "^1.15.0" ruff = "^0.11.10" -types-requests = "^2.32.0" [build-system] requires = ["poetry-core>=2.0.0,<3.0.0"] diff --git a/scripts/csv_import.py b/scripts/csv_import.py index f6dc45e..eb18cb3 100755 --- a/scripts/csv_import.py +++ b/scripts/csv_import.py @@ -12,7 +12,6 @@ from datetime import datetime from pathlib import Path from typing import List, Optional -import requests from cvec import CVec from cvec.models.metric import MetricDataPoint @@ -211,11 +210,11 @@ def main() -> None: metric_prefix=args.prefix, ) - except requests.HTTPError as e: + except HTTPError as e: print(f"Error: {e}") # Display CloudFront ID if available - if e.response is not None: - cf_id = e.response.headers.get("x-amz-cf-id") + if hasattr(e, "headers"): + cf_id = e.headers.get("x-amz-cf-id") if cf_id: print(f"Cf-Id: {cf_id}") sys.exit(1) diff --git a/src/cvec/cvec.py b/src/cvec/cvec.py index 534e396..efa8238 100644 --- a/src/cvec/cvec.py +++ b/src/cvec/cvec.py @@ -1,10 +1,11 @@ +import json import logging import os from datetime import datetime from typing import Any, Dict, List, Optional -from urllib.parse import urljoin - -import requests +from urllib.error import HTTPError, URLError +from urllib.parse import urlencode, urljoin +from urllib.request import Request, urlopen from cvec.models.metric import Metric, MetricDataPoint from cvec.models.span import Span @@ -102,60 +103,73 @@ def _make_request( method: str, endpoint: str, params: Optional[Dict[str, Any]] = None, - json: Optional[Dict[str, Any]] = None, + json_data: Optional[Dict[str, Any]] = None, data: Optional[bytes] = None, headers: Optional[Dict[str, str]] = None, ) -> Any: """Helper method to make HTTP requests.""" url = urljoin(self.host or "", endpoint) + + if params: + filtered_params = {k: v for k, v in params.items() if v is not None} + if filtered_params: + url = f"{url}?{urlencode(filtered_params)}" + request_headers = self._get_headers() if headers: request_headers.update(headers) - response = requests.request( - method=method, - url=url, - headers=request_headers, - params=params, - json=json, - data=data, - ) + request_body = None + if json_data is not None: + request_body = json.dumps(json_data).encode("utf-8") + elif data is not None: + request_body = data + + def make_http_request() -> Any: + """Inner function to make the actual HTTP request.""" + req = Request( + url, data=request_body, headers=request_headers, method=method + ) + with urlopen(req) as response: + response_data = response.read() + content_type = response.headers.get("content-type", "") + + if content_type == "application/vnd.apache.arrow.stream": + return response_data + return json.loads(response_data.decode("utf-8")) - if response.status_code == 401 and self._access_token and self._refresh_token: - try: - self._refresh_supabase_token() - # Update headers with new token - request_headers = self._get_headers() - if headers: - request_headers.update(headers) - - # Retry the request - response = requests.request( - method=method, - url=url, - headers=request_headers, - params=params, - json=json, - data=data, - ) - except (requests.RequestException, ValueError, KeyError) as e: - logger.warning( - "Token refresh failed, continuing with original request: %s", - e, - exc_info=True, - ) - # If refresh fails, continue with the original error response - # which will be raised by raise_for_status() below - pass - - response.raise_for_status() - - if ( - response.headers.get("content-type") - == "application/vnd.apache.arrow.stream" - ): - return response.content - return response.json() + try: + return make_http_request() + except HTTPError as e: + # Handle 401 Unauthorized with token refresh + if e.code == 401 and self._access_token and self._refresh_token: + try: + self._refresh_supabase_token() + # Update headers with new token + request_headers = self._get_headers() + if headers: + request_headers.update(headers) + + # Retry the request + req = Request( + url, data=request_body, headers=request_headers, method=method + ) + with urlopen(req) as response: + response_data = response.read() + content_type = response.headers.get("content-type", "") + + if content_type == "application/vnd.apache.arrow.stream": + return response_data + return json.loads(response_data.decode("utf-8")) + except (HTTPError, URLError, ValueError, KeyError) as refresh_error: + logger.warning( + "Token refresh failed, continuing with original request: %s", + refresh_error, + exc_info=True, + ) + # If refresh fails, re-raise the original 401 error + raise e + raise def get_spans( self, @@ -311,7 +325,7 @@ def add_metric_data( data_dicts: List[Dict[str, Any]] = [ point.model_dump(mode="json") for point in data_points ] - self._make_request("POST", endpoint, json=data_dicts) # type: ignore[arg-type] + self._make_request("POST", endpoint, json_data=data_dicts) # type: ignore[arg-type] def get_modeling_metrics( self, @@ -422,10 +436,12 @@ def _login_with_supabase(self, email: str, password: str) -> None: "apikey": self._publishable_key, } - response = requests.post(supabase_url, json=payload, headers=headers) - response.raise_for_status() + request_body = json.dumps(payload).encode("utf-8") + req = Request(supabase_url, data=request_body, headers=headers, method="POST") - data = response.json() + with urlopen(req) as response: + response_data = response.read() + data = json.loads(response_data.decode("utf-8")) self._access_token = data["access_token"] self._refresh_token = data["refresh_token"] @@ -447,10 +463,13 @@ def _refresh_supabase_token(self) -> None: "apikey": self._publishable_key, } - response = requests.post(supabase_url, json=payload, headers=headers) - response.raise_for_status() + request_body = json.dumps(payload).encode("utf-8") + req = Request(supabase_url, data=request_body, headers=headers, method="POST") + + with urlopen(req) as response: + response_data = response.read() + data = json.loads(response_data.decode("utf-8")) - data = response.json() self._access_token = data["access_token"] self._refresh_token = data["refresh_token"] @@ -466,10 +485,12 @@ def _fetch_publishable_key(self) -> str: """ try: config_url = f"{self.host}/config" - response = requests.get(config_url) - response.raise_for_status() + req = Request(config_url, method="GET") + + with urlopen(req) as response: + response_data = response.read() + config_data = json.loads(response_data.decode("utf-8")) - config_data = response.json() publishable_key = config_data.get("supabasePublishableKey") if not publishable_key: @@ -477,7 +498,7 @@ def _fetch_publishable_key(self) -> str: return str(publishable_key) - except requests.RequestException as e: + except (HTTPError, URLError) as e: raise ValueError(f"Failed to fetch config from {self.host}/config: {e}") except (KeyError, ValueError) as e: raise ValueError(f"Invalid config response: {e}") diff --git a/tests/test_token_refresh.py b/tests/test_token_refresh.py index 0c9d780..4af3d07 100644 --- a/tests/test_token_refresh.py +++ b/tests/test_token_refresh.py @@ -1,9 +1,9 @@ """Tests for token refresh functionality.""" import pytest -import requests from typing import Any from unittest.mock import Mock, patch +from urllib.error import HTTPError from cvec import CVec @@ -13,10 +13,10 @@ class TestTokenRefresh: @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.object(CVec, "_fetch_publishable_key", return_value="test_publishable_key") - @patch("cvec.cvec.requests.request") + @patch("cvec.cvec.urlopen") def test_token_refresh_on_401( self, - mock_request: Any, + mock_urlopen: Any, mock_fetch_key: Any, mock_login: Any, ) -> None: @@ -29,18 +29,25 @@ def test_token_refresh_on_401( client._access_token = "expired_token" client._refresh_token = "valid_refresh_token" - # Mock response sequence - mock_response_401 = Mock() - mock_response_401.status_code = 401 + # Mock 401 error response + http_error_401 = HTTPError( + url="https://test.example.com/api/metrics/", + code=401, + msg="Unauthorized", + hdrs={}, # type: ignore[arg-type] + fp=None, # type: ignore[arg-type] + ) - mock_response_success = Mock() - mock_response_success.status_code = 200 - mock_response_success.headers = {"content-type": "application/json"} - mock_response_success.json.return_value = [] + # Mock successful response after refresh + mock_success_response = Mock() + mock_success_response.read.return_value = b"[]" + mock_success_response.headers = {"content-type": "application/json"} + mock_success_response.__enter__ = Mock(return_value=mock_success_response) + mock_success_response.__exit__ = Mock(return_value=False) - mock_request.side_effect = [ - mock_response_401, - mock_response_success, + mock_urlopen.side_effect = [ + http_error_401, + mock_success_response, ] # Mock refresh method @@ -61,14 +68,16 @@ def mock_refresh() -> None: @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.object(CVec, "_fetch_publishable_key", return_value="test_publishable_key") - @patch("cvec.cvec.requests.request") + @patch("cvec.cvec.urlopen") def test_token_refresh_handles_network_errors_gracefully( self, - mock_request: Any, + mock_urlopen: Any, mock_fetch_key: Any, mock_login: Any, ) -> None: """Test that network errors during refresh don't crash, returns original error.""" + from urllib.error import URLError + client = CVec( host="https://test.example.com", api_key="cva_hHs0CbkKALxMnxUdI9hanF0TBPvvvr1HjG6O", @@ -77,32 +86,35 @@ def test_token_refresh_handles_network_errors_gracefully( client._access_token = "expired_token" client._refresh_token = "valid_refresh_token" - # Mock response: 401 triggers refresh - mock_response_401 = Mock() - mock_response_401.status_code = 401 - mock_response_401.raise_for_status.side_effect = requests.HTTPError( - "401 Client Error: Unauthorized" + # Mock 401 error response + http_error_401 = HTTPError( + url="https://test.example.com/api/metrics/", + code=401, + msg="Unauthorized", + hdrs={}, # type: ignore[arg-type] + fp=None, # type: ignore[arg-type] ) - mock_request.return_value = mock_response_401 + mock_urlopen.side_effect = http_error_401 # Mock refresh to raise network error def mock_refresh_with_error() -> None: - raise requests.ConnectionError("Network unreachable") + raise URLError("Network unreachable") with patch.object( client, "_refresh_supabase_token", side_effect=mock_refresh_with_error ): # Should not crash, should raise the original 401 error - with pytest.raises(requests.HTTPError, match="401"): + with pytest.raises(HTTPError) as exc_info: client.get_metrics() + assert exc_info.value.code == 401 @patch.object(CVec, "_login_with_supabase", return_value=None) @patch.object(CVec, "_fetch_publishable_key", return_value="test_publishable_key") - @patch("cvec.cvec.requests.request") + @patch("cvec.cvec.urlopen") def test_token_refresh_handles_missing_refresh_token( self, - mock_request: Any, + mock_urlopen: Any, mock_fetch_key: Any, mock_login: Any, ) -> None: @@ -115,14 +127,16 @@ def test_token_refresh_handles_missing_refresh_token( client._access_token = "expired_token" client._refresh_token = "valid_refresh_token" - # Mock response: 401 triggers refresh - mock_response_401 = Mock() - mock_response_401.status_code = 401 - mock_response_401.raise_for_status.side_effect = requests.HTTPError( - "401 Client Error: Unauthorized" + # Mock 401 error response + http_error_401 = HTTPError( + url="https://test.example.com/api/metrics/", + code=401, + msg="Unauthorized", + hdrs={}, # type: ignore[arg-type] + fp=None, # type: ignore[arg-type] ) - mock_request.return_value = mock_response_401 + mock_urlopen.side_effect = http_error_401 # Mock refresh to raise ValueError (missing refresh token) def mock_refresh_with_error() -> None: @@ -132,5 +146,6 @@ def mock_refresh_with_error() -> None: client, "_refresh_supabase_token", side_effect=mock_refresh_with_error ): # Should not crash, should raise the original 401 error - with pytest.raises(requests.HTTPError, match="401"): + with pytest.raises(HTTPError) as exc_info: client.get_metrics() + assert exc_info.value.code == 401 From a13e609a2179c2c938c18573d726a54dbe5b09e6 Mon Sep 17 00:00:00 2001 From: Joshua Napoli Date: Thu, 20 Nov 2025 14:35:43 -0500 Subject: [PATCH 3/6] fix: add type guards for publishable_key in auth methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add checks to ensure _publishable_key is not None before using it in headers dictionaries. This fixes mypy strict mode type errors while also improving runtime safety. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/cvec/cvec.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cvec/cvec.py b/src/cvec/cvec.py index efa8238..eacb6a7 100644 --- a/src/cvec/cvec.py +++ b/src/cvec/cvec.py @@ -426,6 +426,9 @@ def _login_with_supabase(self, email: str, password: str) -> None: email: User email password: User password """ + if not self._publishable_key: + raise ValueError("Publishable key not available") + supabase_url = f"{self.host}/supabase/auth/v1/token?grant_type=password" payload = {"email": email, "password": password} @@ -452,6 +455,8 @@ def _refresh_supabase_token(self) -> None: """ if not self._refresh_token: raise ValueError("No refresh token available") + if not self._publishable_key: + raise ValueError("Publishable key not available") supabase_url = f"{self.host}/supabase/auth/v1/token?grant_type=refresh_token" From f10856140cec68a9ccad00a7500102a5a8cf2b2c Mon Sep 17 00:00:00 2001 From: Joshua Napoli Date: Thu, 20 Nov 2025 15:24:59 -0500 Subject: [PATCH 4/6] fix: removed unused 'type: ignore' statements --- tests/test_token_refresh.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_token_refresh.py b/tests/test_token_refresh.py index 4af3d07..6731aa9 100644 --- a/tests/test_token_refresh.py +++ b/tests/test_token_refresh.py @@ -1,10 +1,11 @@ """Tests for token refresh functionality.""" -import pytest from typing import Any from unittest.mock import Mock, patch from urllib.error import HTTPError +import pytest + from cvec import CVec @@ -35,7 +36,7 @@ def test_token_refresh_on_401( code=401, msg="Unauthorized", hdrs={}, # type: ignore[arg-type] - fp=None, # type: ignore[arg-type] + fp=None, ) # Mock successful response after refresh @@ -92,7 +93,7 @@ def test_token_refresh_handles_network_errors_gracefully( code=401, msg="Unauthorized", hdrs={}, # type: ignore[arg-type] - fp=None, # type: ignore[arg-type] + fp=None, ) mock_urlopen.side_effect = http_error_401 @@ -133,7 +134,7 @@ def test_token_refresh_handles_missing_refresh_token( code=401, msg="Unauthorized", hdrs={}, # type: ignore[arg-type] - fp=None, # type: ignore[arg-type] + fp=None, ) mock_urlopen.side_effect = http_error_401 From 93d14f40219ac3c40f22b595315fc46d110fb551 Mon Sep 17 00:00:00 2001 From: Joshua Napoli Date: Thu, 20 Nov 2025 15:29:54 -0500 Subject: [PATCH 5/6] fix: remove unnecessary comments --- tests/test_token_refresh.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_token_refresh.py b/tests/test_token_refresh.py index 6731aa9..890c06b 100644 --- a/tests/test_token_refresh.py +++ b/tests/test_token_refresh.py @@ -30,7 +30,6 @@ def test_token_refresh_on_401( client._access_token = "expired_token" client._refresh_token = "valid_refresh_token" - # Mock 401 error response http_error_401 = HTTPError( url="https://test.example.com/api/metrics/", code=401, @@ -128,7 +127,6 @@ def test_token_refresh_handles_missing_refresh_token( client._access_token = "expired_token" client._refresh_token = "valid_refresh_token" - # Mock 401 error response http_error_401 = HTTPError( url="https://test.example.com/api/metrics/", code=401, From ca4f93ddeb715866a036533f1ecd813bfaf6c1a8 Mon Sep 17 00:00:00 2001 From: Joshua Napoli Date: Thu, 20 Nov 2025 16:33:37 -0500 Subject: [PATCH 6/6] fix: add missing HTTPError import in csv_import script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the missing import for HTTPError from urllib.error that was referenced but not imported after removing the requests dependency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- scripts/csv_import.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/csv_import.py b/scripts/csv_import.py index eb18cb3..16c1f4b 100755 --- a/scripts/csv_import.py +++ b/scripts/csv_import.py @@ -12,6 +12,7 @@ from datetime import datetime from pathlib import Path from typing import List, Optional +from urllib.error import HTTPError from cvec import CVec from cvec.models.metric import MetricDataPoint