cvec 1.5.2__tar.gz → 1.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {cvec-1.5.2 → cvec-1.5.3}/.github/workflows/ci.yml +1 -1
  2. {cvec-1.5.2 → cvec-1.5.3}/.github/workflows/license-check-python.yml +1 -1
  3. cvec-1.5.3/.github/workflows/pyproject-license-check.yml +34 -0
  4. {cvec-1.5.2 → cvec-1.5.3}/PKG-INFO +2 -2
  5. {cvec-1.5.2 → cvec-1.5.3}/pyproject.toml +1 -1
  6. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/cvec.py +12 -9
  7. {cvec-1.5.2 → cvec-1.5.3}/tests/test_cvec.py +89 -0
  8. {cvec-1.5.2 → cvec-1.5.3}/uv.lock +3 -3
  9. cvec-1.5.2/.claude/settings.local.json +0 -28
  10. cvec-1.5.2/.github/workflows/pyproject-license-check.yml +0 -17
  11. {cvec-1.5.2 → cvec-1.5.3}/.claude/settings.json +0 -0
  12. {cvec-1.5.2 → cvec-1.5.3}/.gitignore +0 -0
  13. {cvec-1.5.2 → cvec-1.5.3}/CLAUDE.md +0 -0
  14. {cvec-1.5.2 → cvec-1.5.3}/LICENSE +0 -0
  15. {cvec-1.5.2 → cvec-1.5.3}/README.md +0 -0
  16. {cvec-1.5.2 → cvec-1.5.3}/examples/add_agent_post_example.py +0 -0
  17. {cvec-1.5.2 → cvec-1.5.3}/examples/add_metric_data_arrow_example.py +0 -0
  18. {cvec-1.5.2 → cvec-1.5.3}/examples/add_metric_data_example.py +0 -0
  19. {cvec-1.5.2 → cvec-1.5.3}/examples/add_multiple_metrics_and_get_spans_for_them.py +0 -0
  20. {cvec-1.5.2 → cvec-1.5.3}/examples/eav_example.py +0 -0
  21. {cvec-1.5.2 → cvec-1.5.3}/examples/get_metric_arrow_example.py +0 -0
  22. {cvec-1.5.2 → cvec-1.5.3}/examples/get_metric_data_objects_example.py +0 -0
  23. {cvec-1.5.2 → cvec-1.5.3}/examples/get_metrics_example.py +0 -0
  24. {cvec-1.5.2 → cvec-1.5.3}/examples/get_modeling_metrics_data_arrow_example.py +0 -0
  25. {cvec-1.5.2 → cvec-1.5.3}/examples/get_modeling_metrics_data_example.py +0 -0
  26. {cvec-1.5.2 → cvec-1.5.3}/examples/get_modeling_metrics_example.py +0 -0
  27. {cvec-1.5.2 → cvec-1.5.3}/examples/get_spans_example.py +0 -0
  28. {cvec-1.5.2 → cvec-1.5.3}/examples/show_eav_schema.py +0 -0
  29. {cvec-1.5.2 → cvec-1.5.3}/scripts/csv_import.py +0 -0
  30. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/__init__.py +0 -0
  31. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/http_cache.py +0 -0
  32. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/__init__.py +0 -0
  33. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/agent_post.py +0 -0
  34. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/eav_column.py +0 -0
  35. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/eav_filter.py +0 -0
  36. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/eav_table.py +0 -0
  37. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/metric.py +0 -0
  38. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/models/span.py +0 -0
  39. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/py.typed +0 -0
  40. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/utils/__init__.py +0 -0
  41. {cvec-1.5.2 → cvec-1.5.3}/src/cvec/utils/arrow_converter.py +0 -0
  42. {cvec-1.5.2 → cvec-1.5.3}/tests/__init__.py +0 -0
  43. {cvec-1.5.2 → cvec-1.5.3}/tests/test_http_cache.py +0 -0
  44. {cvec-1.5.2 → cvec-1.5.3}/tests/test_http_compression.py +0 -0
  45. {cvec-1.5.2 → cvec-1.5.3}/tests/test_modeling.py +0 -0
  46. {cvec-1.5.2 → cvec-1.5.3}/tests/test_token_refresh.py +0 -0
@@ -27,7 +27,7 @@ jobs:
27
27
  - name: Checkout
28
28
  uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
29
29
 
30
- - uses: CVector-Energy/python-test@uv
30
+ - uses: CVector-Energy/python-test@b956bb181831f0ddca3496505d5823c678a926e7 # v1.1.0
31
31
  with:
32
32
  python-version: ${{ matrix.python-version }}
33
33
  src-dirs: .
@@ -11,7 +11,7 @@ jobs:
11
11
  - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
12
12
 
13
13
  - name: Check Python licenses
14
- uses: CVector-Energy/pyproject-license-check@main
14
+ uses: CVector-Energy/pyproject-license-check@977092b2a13e0766082dfdfb7c57b67985c675e8 # main
15
15
  with:
16
16
  app-id: ${{ vars.APP_ID }}
17
17
  app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
@@ -0,0 +1,34 @@
1
+ name: Python License Check
2
+
3
+ on:
4
+ push:
5
+
6
+ permissions:
7
+ contents: read
8
+ id-token: write
9
+
10
+ jobs:
11
+ license-check:
12
+ runs-on: ubuntu-24.04
13
+
14
+ steps:
15
+ - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
16
+
17
+ - name: Configure AWS credentials
18
+ uses: aws-actions/configure-aws-credentials@7474bc4690e29a8392af63c5b98e7449536d5c3a # v4.3.1
19
+ with:
20
+ role-to-assume: arn:aws:iam::904233131484:role/CvecCodeArtifactInternalReadOnlyAccess
21
+ aws-region: us-east-1
22
+
23
+ - name: Configure CodeArtifact
24
+ run: |
25
+ CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token --domain cvector --domain-owner 904233131484 --region us-east-1 --query authorizationToken --output text)
26
+ echo "::add-mask::$CODEARTIFACT_AUTH_TOKEN"
27
+ echo "UV_INDEX_CVECTOR_INTERNAL_USERNAME=aws" >> $GITHUB_ENV
28
+ echo "UV_INDEX_CVECTOR_INTERNAL_PASSWORD=${CODEARTIFACT_AUTH_TOKEN}" >> $GITHUB_ENV
29
+
30
+ - name: Check Python licenses
31
+ uses: CVector-Energy/pyproject-license-check@075858d33ce078e040088fbef523db1e0cfd2d63 # v0.3.0
32
+ with:
33
+ app-id: ${{ vars.APP_ID }}
34
+ app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cvec
3
- Version: 1.5.2
3
+ Version: 1.5.3
4
4
  Summary: SDK for CVector Energy
5
5
  Author-email: CVector <support@cvector.energy>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cvec"
3
- version = "1.5.2"
3
+ version = "1.5.3"
4
4
  description = "SDK for CVector Energy"
5
5
  authors = [{ name = "CVector", email = "support@cvector.energy" }]
6
6
  readme = "README.md"
@@ -36,6 +36,7 @@ class CVec:
36
36
  host: Optional[str]
37
37
  default_start_at: Optional[datetime]
38
38
  default_end_at: Optional[datetime]
39
+ timeout: Optional[float]
39
40
  # Supabase authentication
40
41
  _access_token: Optional[str]
41
42
  _refresh_token: Optional[str]
@@ -49,10 +50,12 @@ class CVec:
49
50
  default_start_at: Optional[datetime] = None,
50
51
  default_end_at: Optional[datetime] = None,
51
52
  api_key: Optional[str] = None,
53
+ timeout: Optional[float] = 30,
52
54
  ) -> None:
53
55
  self.host = host or os.environ.get("CVEC_HOST")
54
56
  self.default_start_at = default_start_at
55
57
  self.default_end_at = default_end_at
58
+ self.timeout = timeout
56
59
 
57
60
  # Supabase authentication
58
61
  self._access_token = None
@@ -210,7 +213,7 @@ class CVec:
210
213
  req = Request(
211
214
  url, data=request_body, headers=request_headers, method=method
212
215
  )
213
- with urlopen(req) as response:
216
+ with urlopen(req, timeout=self.timeout) as response:
214
217
  return self._process_response(response, url, method)
215
218
 
216
219
  try:
@@ -234,7 +237,7 @@ class CVec:
234
237
  req = Request(
235
238
  url, data=request_body, headers=request_headers, method=method
236
239
  )
237
- with urlopen(req) as response:
240
+ with urlopen(req, timeout=self.timeout) as response:
238
241
  return self._process_response(response, url, method)
239
242
  except (HTTPError, URLError, ValueError, KeyError) as refresh_error:
240
243
  logger.warning(
@@ -557,7 +560,7 @@ class CVec:
557
560
  request_body = json.dumps(payload).encode("utf-8")
558
561
  req = Request(supabase_url, data=request_body, headers=headers, method="POST")
559
562
 
560
- with urlopen(req) as response:
563
+ with urlopen(req, timeout=self.timeout) as response:
561
564
  response_data = response.read()
562
565
  data = json.loads(response_data.decode("utf-8"))
563
566
 
@@ -586,7 +589,7 @@ class CVec:
586
589
  request_body = json.dumps(payload).encode("utf-8")
587
590
  req = Request(supabase_url, data=request_body, headers=headers, method="POST")
588
591
 
589
- with urlopen(req) as response:
592
+ with urlopen(req, timeout=self.timeout) as response:
590
593
  response_data = response.read()
591
594
  data = json.loads(response_data.decode("utf-8"))
592
595
 
@@ -609,7 +612,7 @@ class CVec:
609
612
  config_url = f"{self.host}/config"
610
613
  req = Request(config_url, method="GET")
611
614
 
612
- with urlopen(req) as response:
615
+ with urlopen(req, timeout=self.timeout) as response:
613
616
  response_data = response.read()
614
617
  config_data = json.loads(response_data.decode("utf-8"))
615
618
 
@@ -664,7 +667,7 @@ class CVec:
664
667
  def make_rpc_request() -> Any:
665
668
  """Inner function to make the actual RPC request."""
666
669
  req = Request(url, data=request_body, headers=headers, method="POST")
667
- with urlopen(req) as response:
670
+ with urlopen(req, timeout=self.timeout) as response:
668
671
  response_data = response.read()
669
672
  return json.loads(response_data.decode("utf-8"))
670
673
 
@@ -682,7 +685,7 @@ class CVec:
682
685
  req = Request(
683
686
  url, data=request_body, headers=headers, method="POST"
684
687
  )
685
- with urlopen(req) as response:
688
+ with urlopen(req, timeout=self.timeout) as response:
686
689
  response_data = response.read()
687
690
  return json.loads(response_data.decode("utf-8"))
688
691
  except (HTTPError, URLError, ValueError, KeyError) as refresh_error:
@@ -731,7 +734,7 @@ class CVec:
731
734
  def make_query_request() -> Any:
732
735
  """Inner function to make the actual query request."""
733
736
  req = Request(url, headers=headers, method="GET")
734
- with urlopen(req) as response:
737
+ with urlopen(req, timeout=self.timeout) as response:
735
738
  response_data = response.read()
736
739
  return json.loads(response_data.decode("utf-8"))
737
740
 
@@ -747,7 +750,7 @@ class CVec:
747
750
 
748
751
  # Retry the request
749
752
  req = Request(url, headers=headers, method="GET")
750
- with urlopen(req) as response:
753
+ with urlopen(req, timeout=self.timeout) as response:
751
754
  response_data = response.read()
752
755
  return json.loads(response_data.decode("utf-8"))
753
756
  except (HTTPError, URLError, ValueError, KeyError) as refresh_error:
@@ -1,5 +1,8 @@
1
1
  import io
2
2
  import os
3
+ import socket
4
+ import threading
5
+ import time
3
6
  from datetime import datetime
4
7
  from typing import Any
5
8
  from unittest.mock import patch
@@ -897,3 +900,89 @@ class TestCVecSelectFromEAVId:
897
900
  )
898
901
 
899
902
  assert result == []
903
+
904
+
905
+ class TestCVecTimeout:
906
+ """Test that HTTP requests respect the timeout parameter."""
907
+
908
+ @patch.object(CVec, "_login_with_supabase", return_value=None)
909
+ @patch.object(
910
+ CVec, "_fetch_config", autospec=True, side_effect=mock_fetch_config_side_effect
911
+ )
912
+ def test_default_timeout_is_set(self, mock_fetch_key: Any, mock_login: Any) -> None:
913
+ """Test that the default timeout is 30 seconds."""
914
+ client = CVec(
915
+ host="test_host",
916
+ api_key="cva_hHs0CbkKALxMnxUdI9hanF0TBPvvvr1HjG6O",
917
+ )
918
+ assert client.timeout == 30
919
+
920
+ @patch.object(CVec, "_login_with_supabase", return_value=None)
921
+ @patch.object(
922
+ CVec, "_fetch_config", autospec=True, side_effect=mock_fetch_config_side_effect
923
+ )
924
+ def test_custom_timeout_is_set(self, mock_fetch_key: Any, mock_login: Any) -> None:
925
+ """Test that a custom timeout can be provided."""
926
+ client = CVec(
927
+ host="test_host",
928
+ api_key="cva_hHs0CbkKALxMnxUdI9hanF0TBPvvvr1HjG6O",
929
+ timeout=60,
930
+ )
931
+ assert client.timeout == 60
932
+
933
+ @patch.object(CVec, "_login_with_supabase", return_value=None)
934
+ @patch.object(
935
+ CVec, "_fetch_config", autospec=True, side_effect=mock_fetch_config_side_effect
936
+ )
937
+ def test_none_timeout_disables_timeout(
938
+ self, mock_fetch_key: Any, mock_login: Any
939
+ ) -> None:
940
+ """Test that timeout=None disables the timeout."""
941
+ client = CVec(
942
+ host="test_host",
943
+ api_key="cva_hHs0CbkKALxMnxUdI9hanF0TBPvvvr1HjG6O",
944
+ timeout=None,
945
+ )
946
+ assert client.timeout is None
947
+
948
+ def test_make_request_times_out_on_hung_server(self) -> None:
949
+ """Test that _make_request raises when the server hangs."""
950
+ srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
951
+ srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
952
+ srv.bind(("127.0.0.1", 0))
953
+ srv.listen(1)
954
+ port = srv.getsockname()[1]
955
+
956
+ def accept_and_hang() -> None:
957
+ conn, _ = srv.accept()
958
+ time.sleep(30)
959
+ conn.close()
960
+ srv.close()
961
+
962
+ t = threading.Thread(target=accept_and_hang, daemon=True)
963
+ t.start()
964
+
965
+ with (
966
+ patch.object(CVec, "_login_with_supabase", return_value=None),
967
+ patch.object(
968
+ CVec,
969
+ "_fetch_config",
970
+ autospec=True,
971
+ side_effect=mock_fetch_config_side_effect,
972
+ ),
973
+ ):
974
+ client = CVec(
975
+ host=f"http://127.0.0.1:{port}",
976
+ api_key="cva_hHs0CbkKALxMnxUdI9hanF0TBPvvvr1HjG6O",
977
+ timeout=2,
978
+ )
979
+ client._access_token = "fake_token"
980
+
981
+ start = time.time()
982
+ with pytest.raises(OSError):
983
+ client._make_request("GET", "/api/test")
984
+ elapsed = time.time() - start
985
+
986
+ assert elapsed < 5, f"Expected timeout around 2s, took {elapsed:.1f}s"
987
+
988
+ srv.close()
@@ -399,11 +399,11 @@ wheels = [
399
399
 
400
400
  [[package]]
401
401
  name = "pygments"
402
- version = "2.19.1"
402
+ version = "2.20.0"
403
403
  source = { registry = "https://pypi.org/simple" }
404
- sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" }
404
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
405
405
  wheels = [
406
- { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" },
406
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
407
407
  ]
408
408
 
409
409
  [[package]]
@@ -1,28 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(chmod:*)",
5
- "Bash(python:*)",
6
- "Bash(git commit:*)",
7
- "Bash(pytest:*)",
8
- "Bash(python3 -m pytest:*)",
9
- "Bash(poetry run pytest:*)",
10
- "Bash(git add:*)",
11
- "Bash(poetry run mypy:*)",
12
- "Bash(git checkout:*)",
13
- "Bash(poetry run ruff:*)",
14
- "Bash(poetry install:*)",
15
- "Bash(tree:*)",
16
- "Bash(git mv:*)",
17
- "WebFetch(domain:github.com)",
18
- "WebFetch(domain:pypi.org)",
19
- "Bash(poetry run python:*)",
20
- "Bash(poetry add:*)",
21
- "Bash(git fetch:*)",
22
- "Bash(git rebase:*)",
23
- "Bash(git rm:*)"
24
- ],
25
- "deny": [],
26
- "ask": []
27
- }
28
- }
@@ -1,17 +0,0 @@
1
- name: Python License Check
2
-
3
- on:
4
- push:
5
-
6
- jobs:
7
- license-check:
8
- runs-on: ubuntu-24.04
9
-
10
- steps:
11
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
12
-
13
- - name: Check Python licenses
14
- uses: CVector-Energy/pyproject-license-check@main
15
- with:
16
- app-id: ${{ vars.APP_ID }}
17
- app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes