diff --git a/pyproject.toml b/pyproject.toml index ddb9119..00b77d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ dependencies = [ "mcp>=1.6.0", "langchain-mcp-adapters>=0.0.9", "langchain-deepseek>=0.1.3", - "volcengine>=1.0.191", ] [project.optional-dependencies] diff --git a/src/rag/vikingdb_knowledge_base.py b/src/rag/vikingdb_knowledge_base.py index 9c235e7..23f68cc 100644 --- a/src/rag/vikingdb_knowledge_base.py +++ b/src/rag/vikingdb_knowledge_base.py @@ -4,11 +4,12 @@ import os import requests import json +import hashlib +import hmac +import urllib.parse +from datetime import datetime from src.rag.retriever import Chunk, Document, Resource, Retriever from urllib.parse import urlparse -from volcengine.auth.SignerV4 import SignerV4 -from volcengine.base.Request import Request -from volcengine.Credentials import Credentials class VikingDBKnowledgeBaseProvider(Retriever): @@ -20,6 +21,8 @@ class VikingDBKnowledgeBaseProvider(Retriever): api_ak: str api_sk: str retrieval_size: int = 10 + region: str = "cn-north-1" + service: str = "air" def __init__(self): api_url = os.getenv("VIKINGDB_KNOWLEDGE_BASE_API_URL") @@ -41,41 +44,137 @@ class VikingDBKnowledgeBaseProvider(Retriever): if retrieval_size: self.retrieval_size = int(retrieval_size) - def prepare_request(self, method, path, params=None, data=None, doseq=0): - """ - Prepare signed request using volcengine auth - """ - if params: - for key in params: - if ( - type(params[key]) is int - or type(params[key]) is float - or type(params[key]) is bool - ): - params[key] = str(params[key]) - elif type(params[key]) is list: - if not doseq: - params[key] = ",".join(params[key]) + # 设置region,如果需要可以从环境变量获取 + region = os.getenv("VIKINGDB_KNOWLEDGE_BASE_REGION", "cn-north-1") + self.region = region - r = Request() - r.set_shema("https") - r.set_method(method) - r.set_connection_timeout(10) - r.set_socket_timeout(10) - mheaders = { - "Accept": "application/json", - "Content-Type": "application/json", - } - r.set_headers(mheaders) - if params: - r.set_query(params) - r.set_path(path) - if data is not None: - r.set_body(json.dumps(data)) + def _hmac_sha256(self, key: bytes, content: str) -> bytes: + return hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest() - credentials = Credentials(self.api_ak, self.api_sk, "air", "cn-north-1") - SignerV4.sign(r, credentials) - return r + def _hash_sha256(self, data: bytes) -> bytes: + return hashlib.sha256(data).digest() + + def _get_signed_key( + self, secret_key: str, date: str, region: str, service: str + ) -> bytes: + k_date = self._hmac_sha256(secret_key.encode("utf-8"), date) + k_region = self._hmac_sha256(k_date, region) + k_service = self._hmac_sha256(k_region, service) + k_signing = self._hmac_sha256(k_service, "request") + return k_signing + + def _create_canonical_request( + self, method: str, path: str, query_params: dict, headers: dict, payload: bytes + ) -> str: + canonical_method = method.upper() + canonical_uri = path if path else "/" + if query_params: + encoded_params = [] + for key in sorted(query_params.keys()): + value = query_params[key] + encoded_key = urllib.parse.quote(str(key), safe="") + encoded_value = urllib.parse.quote(str(value), safe="") + encoded_params.append(f"{encoded_key}={encoded_value}") + canonical_query_string = "&".join(encoded_params) + else: + canonical_query_string = "" + + canonical_headers_list = [] + signed_headers_list = [] + for header_name in sorted(headers.keys(), key=str.lower): + header_name_lower = header_name.lower() + header_value = str(headers[header_name]).strip() + canonical_headers_list.append(f"{header_name_lower}:{header_value}") + signed_headers_list.append(header_name_lower) + + canonical_headers = "\n".join(canonical_headers_list) + "\n" + signed_headers = ";".join(signed_headers_list) + + payload_hash = self._hash_sha256(payload).hex() + + canonical_request = "\n".join( + [ + canonical_method, + canonical_uri, + canonical_query_string, + canonical_headers, + signed_headers, + payload_hash, + ] + ) + + return canonical_request, signed_headers + + def _create_signature( + self, method: str, path: str, query_params: dict, headers: dict, payload: bytes + ) -> str: + now = datetime.utcnow() + date_stamp = now.strftime("%Y%m%dT%H%M%SZ") + auth_date = date_stamp[:8] + + headers["X-Date"] = date_stamp + headers["Host"] = self.api_url.replace("https://", "").replace("http://", "") + headers["X-Content-Sha256"] = self._hash_sha256(payload).hex() + headers["Content-Type"] = "application/json" + + canonical_request, signed_headers = self._create_canonical_request( + method, path, query_params, headers, payload + ) + + algorithm = "HMAC-SHA256" + credential_scope = f"{auth_date}/{self.region}/{self.service}/request" + canonical_request_hash = self._hash_sha256( + canonical_request.encode("utf-8") + ).hex() + + string_to_sign = "\n".join( + [algorithm, date_stamp, credential_scope, canonical_request_hash] + ) + + signing_key = self._get_signed_key( + self.api_sk, auth_date, self.region, self.service + ) + signature = hmac.new( + signing_key, string_to_sign.encode("utf-8"), hashlib.sha256 + ).hexdigest() + + authorization = ( + f"{algorithm} " + f"Credential={self.api_ak}/{credential_scope}, " + f"SignedHeaders={signed_headers}, " + f"Signature={signature}" + ) + + headers["Authorization"] = authorization + + return headers + + def _make_signed_request( + self, method: str, path: str, params: dict = None, data: dict = None + ): + if data is None: + payload = b"" + else: + payload = json.dumps(data).encode("utf-8") + + if params is None: + params = {} + + url = f"https://{self.api_url}{path}" + headers = {} + signed_headers = self._create_signature(method, path, params, headers, payload) + try: + response = requests.request( + method=method, + url=url, + headers=signed_headers, + params=params, + data=payload if payload else None, + timeout=30, + ) + return response + except Exception as e: + raise ValueError(f"Request failed: {e}") def query_relevant_documents( self, query: str, resources: list[Resource] = [] @@ -111,29 +210,24 @@ class VikingDBKnowledgeBaseProvider(Retriever): query_param = {"doc_filter": doc_filter} request_params["query_param"] = query_param - method = "POST" path = "/api/knowledge/collection/search_knowledge" - info_req = self.prepare_request( - method=method, path=path, data=request_params - ) - rsp = requests.request( - method=info_req.method, - url="http://{}{}".format(self.api_url, info_req.path), - headers=info_req.headers, - data=info_req.body, + + # 使用新的签名请求方法 + response = self._make_signed_request( + method="POST", path=path, data=request_params ) try: - response = json.loads(rsp.text) + response_data = response.json() except json.JSONDecodeError as e: raise ValueError(f"Failed to parse JSON response: {e}") - if response["code"] != 0: + if response_data["code"] != 0: raise ValueError( - f"Failed to query documents from resource: {response['message']}" + f"Failed to query documents from resource: {response_data['message']}" ) - rsp_data = response.get("data", {}) + rsp_data = response_data.get("data", {}) if "result_list" not in rsp_data: continue @@ -163,25 +257,20 @@ class VikingDBKnowledgeBaseProvider(Retriever): """ List resources (knowledge bases) from the knowledge base service """ - method = "POST" path = "/api/knowledge/collection/list" - info_req = self.prepare_request(method=method, path=path) - rsp = requests.request( - method=info_req.method, - url="http://{}{}".format(self.api_url, info_req.path), - headers=info_req.headers, - data=info_req.body, - ) + + response = self._make_signed_request(method="POST", path=path) + try: - response = json.loads(rsp.text) + response_data = response.json() except json.JSONDecodeError as e: raise ValueError(f"Failed to parse JSON response: {e}") - if response["code"] != 0: - raise Exception(f"Failed to list resources: {response["message"]}") + if response_data["code"] != 0: + raise Exception(f"Failed to list resources: {response_data['message']}") resources = [] - rsp_data = response.get("data", {}) + rsp_data = response_data.get("data", {}) collection_list = rsp_data.get("collection_list", []) for item in collection_list: collection_name = item.get("collection_name", "") diff --git a/tests/unit/rag/test_vikingdb_knowledge_base.py b/tests/unit/rag/test_vikingdb_knowledge_base.py index dbbea11..a60fb58 100644 --- a/tests/unit/rag/test_vikingdb_knowledge_base.py +++ b/tests/unit/rag/test_vikingdb_knowledge_base.py @@ -4,7 +4,10 @@ import os import pytest import json +import hashlib +import hmac from unittest.mock import patch, MagicMock +from datetime import datetime from src.rag.vikingdb_knowledge_base import VikingDBKnowledgeBaseProvider, parse_uri @@ -50,6 +53,7 @@ def env_vars(): "VIKINGDB_KNOWLEDGE_BASE_API_AK": "test_ak", "VIKINGDB_KNOWLEDGE_BASE_API_SK": "test_sk", "VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE": "10", + "VIKINGDB_KNOWLEDGE_BASE_REGION": "cn-north-1", }, ): yield @@ -89,6 +93,8 @@ class TestVikingDBKnowledgeBaseProviderInit: assert provider.api_ak == "test_ak" assert provider.api_sk == "test_sk" assert provider.retrieval_size == 10 + assert provider.region == "cn-north-1" + assert provider.service == "air" def test_init_success_without_retrieval_size(self): """Test initialization without VIKINGDB_KNOWLEDGE_BASE_RETRIEVAL_SIZE (should use default)""" @@ -118,6 +124,20 @@ class TestVikingDBKnowledgeBaseProviderInit: provider = VikingDBKnowledgeBaseProvider() assert provider.retrieval_size == 5 + def test_init_custom_region(self): + """Test initialization with custom region""" + with patch.dict( + os.environ, + { + "VIKINGDB_KNOWLEDGE_BASE_API_URL": "api-test.example.com", + "VIKINGDB_KNOWLEDGE_BASE_API_AK": "test_ak", + "VIKINGDB_KNOWLEDGE_BASE_API_SK": "test_sk", + "VIKINGDB_KNOWLEDGE_BASE_REGION": "us-east-1", + }, + ): + provider = VikingDBKnowledgeBaseProvider() + assert provider.region == "us-east-1" + def test_init_missing_api_url(self): """Test initialization fails when API URL is missing""" with patch.dict( @@ -164,61 +184,107 @@ class TestVikingDBKnowledgeBaseProviderInit: VikingDBKnowledgeBaseProvider() -class TestVikingDBKnowledgeBaseProviderPrepareRequest: +class TestVikingDBKnowledgeBaseProviderSignature: @pytest.fixture def provider(self, env_vars): return VikingDBKnowledgeBaseProvider() - def test_prepare_request_basic(self, provider): - """Test basic request preparation""" - with ( - patch("src.rag.vikingdb_knowledge_base.Request") as mock_request, - patch("src.rag.vikingdb_knowledge_base.Credentials") as _mock_credentials, - patch("src.rag.vikingdb_knowledge_base.SignerV4.sign") as _mock_sign, - ): + def test_hmac_sha256(self, provider): + """Test HMAC SHA256 calculation""" + key = b"test_key" + content = "test_content" + result = provider._hmac_sha256(key, content) + expected = hmac.new(key, content.encode("utf-8"), hashlib.sha256).digest() + assert result == expected - mock_req_instance = MagicMock() - mock_request.return_value = mock_req_instance + def test_hash_sha256(self, provider): + """Test SHA256 hash calculation""" + data = b"test_data" + result = provider._hash_sha256(data) + expected = hashlib.sha256(data).digest() + assert result == expected - result = provider.prepare_request("POST", "/test/path") + def test_get_signed_key(self, provider): + """Test signed key generation""" + secret_key = "test_secret" + date = "20250722" + region = "cn-north-1" + service = "air" - assert result == mock_req_instance - mock_req_instance.set_shema.assert_called_once_with("https") - mock_req_instance.set_method.assert_called_once_with("POST") - mock_req_instance.set_path.assert_called_once_with("/test/path") + result = provider._get_signed_key(secret_key, date, region, service) + assert isinstance(result, bytes) + assert len(result) == 32 # SHA256 digest is 32 bytes - def test_prepare_request_with_params(self, provider): - """Test request preparation with parameters""" - with ( - patch("src.rag.vikingdb_knowledge_base.Request") as mock_request, - patch("src.rag.vikingdb_knowledge_base.Credentials"), - patch("src.rag.vikingdb_knowledge_base.SignerV4.sign"), - ): + def test_create_canonical_request(self, provider): + """Test canonical request creation""" + method = "POST" + path = "/api/test" + query_params = {"param1": "value1", "param2": "value2"} + headers = {"Content-Type": "application/json", "Host": "example.com"} + payload = b'{"test": "data"}' - mock_req_instance = MagicMock() - mock_request.return_value = mock_req_instance + canonical_request, signed_headers = provider._create_canonical_request( + method, path, query_params, headers, payload + ) - params = {"key": "value", "number": 123, "boolean": True} - provider.prepare_request("GET", "/test", params=params) + assert "POST" in canonical_request + assert "/api/test" in canonical_request + assert "param1=value1¶m2=value2" in canonical_request + assert "content-type:application/json" in canonical_request + assert "host:example.com" in canonical_request + assert signed_headers == "content-type;host" - expected_params = {"key": "value", "number": "123", "boolean": "True"} - mock_req_instance.set_query.assert_called_once_with(expected_params) + @patch("src.rag.vikingdb_knowledge_base.datetime") + def test_create_signature(self, mock_datetime, provider): + """Test signature creation""" + # Mock datetime + mock_now = datetime(2025, 7, 22, 10, 30, 45) + mock_datetime.utcnow.return_value = mock_now - def test_prepare_request_with_data(self, provider): - """Test request preparation with data""" - with ( - patch("src.rag.vikingdb_knowledge_base.Request") as mock_request, - patch("src.rag.vikingdb_knowledge_base.Credentials"), - patch("src.rag.vikingdb_knowledge_base.SignerV4.sign"), - ): + method = "POST" + path = "/api/test" + query_params = {} + headers = {} + payload = b'{"test": "data"}' - mock_req_instance = MagicMock() - mock_request.return_value = mock_req_instance + result = provider._create_signature( + method, path, query_params, headers, payload + ) - data = {"test": "data"} - provider.prepare_request("POST", "/test", data=data) + assert "X-Date" in result + assert "Host" in result + assert "X-Content-Sha256" in result + assert "Content-Type" in result + assert "Authorization" in result + assert "HMAC-SHA256" in result["Authorization"] - mock_req_instance.set_body.assert_called_once_with(json.dumps(data)) + @patch("src.rag.vikingdb_knowledge_base.requests.request") + def test_make_signed_request_success(self, mock_request, provider): + """Test successful signed request""" + mock_response = MagicMock() + mock_response.json.return_value = {"code": 0, "data": {}} + mock_request.return_value = mock_response + + result = provider._make_signed_request( + "POST", "/api/test", data={"test": "data"} + ) + + assert result == mock_response + mock_request.assert_called_once() + + # Verify the call arguments + call_args = mock_request.call_args + assert call_args[1]["method"] == "POST" + assert call_args[1]["url"] == f"https://{provider.api_url}/api/test" + assert call_args[1]["timeout"] == 30 + + @patch("src.rag.vikingdb_knowledge_base.requests.request") + def test_make_signed_request_with_exception(self, mock_request, provider): + """Test signed request with exception""" + mock_request.side_effect = Exception("Network error") + + with pytest.raises(ValueError, match="Request failed: Network error"): + provider._make_signed_request("GET", "/api/test") class TestVikingDBKnowledgeBaseProviderQueryRelevantDocuments: @@ -231,166 +297,147 @@ class TestVikingDBKnowledgeBaseProviderQueryRelevantDocuments: result = provider.query_relevant_documents("test query", []) assert result == [] - @patch("src.rag.vikingdb_knowledge_base.requests.request") + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") def test_query_relevant_documents_success(self, mock_request, provider): """Test successful document query""" # Mock response mock_response = MagicMock() - mock_response.text = json.dumps( + mock_response.json.return_value = { + "code": 0, + "data": { + "result_list": [ + { + "doc_info": { + "doc_id": "doc123", + "doc_name": "Test Document", + }, + "content": "Test content", + "score": 0.95, + } + ] + }, + } + mock_request.return_value = mock_response + + resources = [MockResource("rag://dataset/123")] + result = provider.query_relevant_documents("test query", resources) + + assert len(result) == 1 + assert result[0].id == "doc123" + assert result[0].title == "Test Document" + assert len(result[0].chunks) == 1 + assert result[0].chunks[0].content == "Test content" + assert result[0].chunks[0].similarity == 0.95 + + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") + def test_query_relevant_documents_with_document_filter( + self, mock_request, provider + ): + """Test document query with document ID filter""" + mock_response = MagicMock() + mock_response.json.return_value = {"code": 0, "data": {"result_list": []}} + mock_request.return_value = mock_response + + resources = [MockResource("rag://dataset/123#doc456")] + provider.query_relevant_documents("test query", resources) + + # Verify that query_param with doc_filter was included in the request + call_args = mock_request.call_args + request_data = call_args[1]["data"] + assert "query_param" in request_data + assert "doc_filter" in request_data["query_param"] + + doc_filter = request_data["query_param"]["doc_filter"] + assert doc_filter["op"] == "must" + assert doc_filter["field"] == "doc_id" + assert doc_filter["conds"] == ["doc456"] + + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") + def test_query_relevant_documents_api_error(self, mock_request, provider): + """Test handling of API error response""" + mock_response = MagicMock() + mock_response.json.return_value = {"code": 1, "message": "API Error"} + mock_request.return_value = mock_response + + resources = [MockResource("rag://dataset/123")] + with pytest.raises( + ValueError, match="Failed to query documents from resource: API Error" + ): + provider.query_relevant_documents("test query", resources) + + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") + def test_query_relevant_documents_json_decode_error(self, mock_request, provider): + """Test handling of JSON decode error""" + mock_response = MagicMock() + mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0) + mock_request.return_value = mock_response + + resources = [MockResource("rag://dataset/123")] + with pytest.raises(ValueError, match="Failed to parse JSON response"): + provider.query_relevant_documents("test query", resources) + + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") + def test_query_relevant_documents_multiple_resources(self, mock_request, provider): + """Test querying multiple resources and merging results""" + # Mock responses for different resources + responses = [ { "code": 0, "data": { "result_list": [ { "doc_info": { - "doc_id": "doc123", - "doc_name": "Test Document", + "doc_id": "doc1", + "doc_name": "Document 1", }, - "content": "Test content", - "score": 0.95, + "content": "Content 1", + "score": 0.9, } ] }, - } - ) - mock_request.return_value = mock_response - - # Mock prepare_request - with patch.object(provider, "prepare_request") as mock_prepare: - mock_req = MagicMock() - mock_req.method = "POST" - mock_req.path = "/api/knowledge/collection/search_knowledge" - mock_req.headers = {} - mock_req.body = "{}" - mock_prepare.return_value = mock_req - - resources = [MockResource("rag://dataset/123")] - result = provider.query_relevant_documents("test query", resources) - - assert len(result) == 1 - assert result[0].id == "doc123" - assert result[0].title == "Test Document" - assert len(result[0].chunks) == 1 - assert result[0].chunks[0].content == "Test content" - assert result[0].chunks[0].similarity == 0.95 - - @patch("src.rag.vikingdb_knowledge_base.requests.request") - def test_query_relevant_documents_with_document_filter( - self, mock_request, provider - ): - """Test document query with document ID filter""" - mock_response = MagicMock() - mock_response.text = json.dumps({"code": 0, "data": {"result_list": []}}) - mock_request.return_value = mock_response - - with patch.object(provider, "prepare_request") as mock_prepare: - mock_req = MagicMock() - mock_prepare.return_value = mock_req - - resources = [MockResource("rag://dataset/123#doc456")] - provider.query_relevant_documents("test query", resources) - - # Verify that query_param with doc_filter was included in the request - call_args = mock_prepare.call_args - request_data = call_args[1]["data"] - assert "query_param" in request_data - assert "doc_filter" in request_data["query_param"] - - doc_filter = request_data["query_param"]["doc_filter"] - assert doc_filter["op"] == "must" - assert doc_filter["field"] == "doc_id" - assert doc_filter["conds"] == ["doc456"] - - @patch("src.rag.vikingdb_knowledge_base.requests.request") - def test_query_relevant_documents_api_error(self, mock_request, provider): - """Test handling of API error response""" - mock_response = MagicMock() - mock_response.text = json.dumps({"code": 1, "message": "API Error"}) - mock_request.return_value = mock_response - - with patch.object(provider, "prepare_request"): - resources = [MockResource("rag://dataset/123")] - with pytest.raises( - ValueError, match="Failed to query documents from resource: API Error" - ): - provider.query_relevant_documents("test query", resources) - - @patch("src.rag.vikingdb_knowledge_base.requests.request") - def test_query_relevant_documents_json_decode_error(self, mock_request, provider): - """Test handling of JSON decode error""" - mock_response = MagicMock() - mock_response.text = "invalid json" - mock_request.return_value = mock_response - - with patch.object(provider, "prepare_request"): - resources = [MockResource("rag://dataset/123")] - with pytest.raises(ValueError, match="Failed to parse JSON response"): - provider.query_relevant_documents("test query", resources) - - @patch("src.rag.vikingdb_knowledge_base.requests.request") - def test_query_relevant_documents_multiple_resources(self, mock_request, provider): - """Test querying multiple resources and merging results""" - # Mock responses for different resources - responses = [ - json.dumps( - { - "code": 0, - "data": { - "result_list": [ - { - "doc_info": { - "doc_id": "doc1", - "doc_name": "Document 1", - }, - "content": "Content 1", - "score": 0.9, - } - ] - }, - } - ), - json.dumps( - { - "code": 0, - "data": { - "result_list": [ - { - "doc_info": { - "doc_id": "doc1", - "doc_name": "Document 1", - }, - "content": "Content 2", - "score": 0.8, + }, + { + "code": 0, + "data": { + "result_list": [ + { + "doc_info": { + "doc_id": "doc1", + "doc_name": "Document 1", }, - { - "doc_info": { - "doc_id": "doc2", - "doc_name": "Document 2", - }, - "content": "Content 3", - "score": 0.7, + "content": "Content 2", + "score": 0.8, + }, + { + "doc_info": { + "doc_id": "doc2", + "doc_name": "Document 2", }, - ] - }, - } - ), + "content": "Content 3", + "score": 0.7, + }, + ] + }, + }, ] - mock_request.side_effect = [MagicMock(text=resp) for resp in responses] + mock_responses = [MagicMock() for _ in responses] + for i, resp in enumerate(responses): + mock_responses[i].json.return_value = resp + mock_request.side_effect = mock_responses - with patch.object(provider, "prepare_request"): - resources = [ - MockResource("rag://dataset/123"), - MockResource("rag://dataset/456"), - ] - result = provider.query_relevant_documents("test query", resources) + resources = [ + MockResource("rag://dataset/123"), + MockResource("rag://dataset/456"), + ] + result = provider.query_relevant_documents("test query", resources) - # Should have 2 documents: doc1 (with 2 chunks) and doc2 (with 1 chunk) - assert len(result) == 2 - doc1 = next(doc for doc in result if doc.id == "doc1") - doc2 = next(doc for doc in result if doc.id == "doc2") - assert len(doc1.chunks) == 2 - assert len(doc2.chunks) == 1 + # Should have 2 documents: doc1 (with 2 chunks) and doc2 (with 1 chunk) + assert len(result) == 2 + doc1 = next(doc for doc in result if doc.id == "doc1") + doc2 = next(doc for doc in result if doc.id == "doc2") + assert len(doc1.chunks) == 2 + assert len(doc2.chunks) == 1 class TestVikingDBKnowledgeBaseProviderListResources: @@ -398,106 +445,94 @@ class TestVikingDBKnowledgeBaseProviderListResources: def provider(self, env_vars): return VikingDBKnowledgeBaseProvider() - @patch("src.rag.vikingdb_knowledge_base.requests.request") + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") def test_list_resources_success(self, mock_request, provider): """Test successful resource listing""" mock_response = MagicMock() - mock_response.text = json.dumps( - { - "code": 0, - "data": { - "collection_list": [ - { - "resource_id": "123", - "collection_name": "Dataset 1", - "description": "Description 1", - }, - { - "resource_id": "456", - "collection_name": "Dataset 2", - "description": "Description 2", - }, - ] - }, - } - ) + mock_response.json.return_value = { + "code": 0, + "data": { + "collection_list": [ + { + "resource_id": "123", + "collection_name": "Dataset 1", + "description": "Description 1", + }, + { + "resource_id": "456", + "collection_name": "Dataset 2", + "description": "Description 2", + }, + ] + }, + } mock_request.return_value = mock_response - with patch.object(provider, "prepare_request") as mock_prepare: - mock_req = MagicMock() - mock_prepare.return_value = mock_req + result = provider.list_resources() - result = provider.list_resources() + assert len(result) == 2 + assert result[0].uri == "rag://dataset/123" + assert result[0].title == "Dataset 1" + assert result[0].description == "Description 1" + assert result[1].uri == "rag://dataset/456" + assert result[1].title == "Dataset 2" + assert result[1].description == "Description 2" - assert len(result) == 2 - assert result[0].uri == "rag://dataset/123" - assert result[0].title == "Dataset 1" - assert result[0].description == "Description 1" - assert result[1].uri == "rag://dataset/456" - assert result[1].title == "Dataset 2" - assert result[1].description == "Description 2" - - @patch("src.rag.vikingdb_knowledge_base.requests.request") + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") def test_list_resources_with_query_filter(self, mock_request, provider): """Test resource listing with query filter""" mock_response = MagicMock() - mock_response.text = json.dumps( - { - "code": 0, - "data": { - "collection_list": [ - { - "resource_id": "123", - "collection_name": "Test Dataset", - "description": "Description", - }, - { - "resource_id": "456", - "collection_name": "Other Dataset", - "description": "Description", - }, - ] - }, - } - ) + mock_response.json.return_value = { + "code": 0, + "data": { + "collection_list": [ + { + "resource_id": "123", + "collection_name": "Test Dataset", + "description": "Description", + }, + { + "resource_id": "456", + "collection_name": "Other Dataset", + "description": "Description", + }, + ] + }, + } mock_request.return_value = mock_response - with patch.object(provider, "prepare_request"): - result = provider.list_resources("test") + result = provider.list_resources("test") - # Should only return the dataset with "test" in the name - assert len(result) == 1 - assert result[0].title == "Test Dataset" + # Should only return the dataset with "test" in the name + assert len(result) == 1 + assert result[0].title == "Test Dataset" - @patch("src.rag.vikingdb_knowledge_base.requests.request") + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") def test_list_resources_api_error(self, mock_request, provider): """Test handling of API error in list_resources""" mock_response = MagicMock() - mock_response.text = json.dumps({"code": 1, "message": "API Error"}) + mock_response.json.return_value = {"code": 1, "message": "API Error"} mock_request.return_value = mock_response - with patch.object(provider, "prepare_request"): - with pytest.raises(Exception, match="Failed to list resources: API Error"): - provider.list_resources() + with pytest.raises(Exception, match="Failed to list resources: API Error"): + provider.list_resources() - @patch("src.rag.vikingdb_knowledge_base.requests.request") + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") def test_list_resources_json_decode_error(self, mock_request, provider): """Test handling of JSON decode error in list_resources""" mock_response = MagicMock() - mock_response.text = "invalid json" + mock_response.json.side_effect = json.JSONDecodeError("Invalid JSON", "", 0) mock_request.return_value = mock_response - with patch.object(provider, "prepare_request"): - with pytest.raises(ValueError, match="Failed to parse JSON response"): - provider.list_resources() + with pytest.raises(ValueError, match="Failed to parse JSON response"): + provider.list_resources() - @patch("src.rag.vikingdb_knowledge_base.requests.request") + @patch.object(VikingDBKnowledgeBaseProvider, "_make_signed_request") def test_list_resources_empty_response(self, mock_request, provider): """Test handling of empty response""" mock_response = MagicMock() - mock_response.text = json.dumps({"code": 0, "data": {"collection_list": []}}) + mock_response.json.return_value = {"code": 0, "data": {"collection_list": []}} mock_request.return_value = mock_response - with patch.object(provider, "prepare_request"): - result = provider.list_resources() - assert result == [] + result = provider.list_resources() + assert result == [] diff --git a/uv.lock b/uv.lock index 5caaa80..b83292b 100644 --- a/uv.lock +++ b/uv.lock @@ -365,15 +365,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, ] -[[package]] -name = "decorator" -version = "5.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, -] - [[package]] name = "deer-flow" version = "0.1.0" @@ -402,7 +393,6 @@ dependencies = [ { name = "socksio" }, { name = "sse-starlette" }, { name = "uvicorn" }, - { name = "volcengine" }, { name = "yfinance" }, ] @@ -449,7 +439,6 @@ requires-dist = [ { name = "socksio", specifier = ">=1.0.0" }, { name = "sse-starlette", specifier = ">=1.6.5" }, { name = "uvicorn", specifier = ">=0.27.1" }, - { name = "volcengine", specifier = ">=1.0.191" }, { name = "yfinance", specifier = ">=0.2.54" }, ] provides-extras = ["dev", "test"] @@ -577,18 +566,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/56/53/eb690efa8513166adef3e0669afd31e95ffde69fb3c52ec2ac7223ed6018/fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3", size = 193615, upload-time = "2025-03-07T21:47:54.809Z" }, ] -[[package]] -name = "google" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "beautifulsoup4" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/89/97/b49c69893cddea912c7a660a4b6102c6b02cd268f8c7162dd70b7c16f753/google-3.0.0.tar.gz", hash = "sha256:143530122ee5130509ad5e989f0512f7cb218b2d4eddbafbad40fd10e8d8ccbe", size = 44978, upload-time = "2020-07-11T14:50:45.678Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/35/17c9141c4ae21e9a29a43acdfd848e3e468a810517f862cad07977bf8fe9/google-3.0.0-py2.py3-none-any.whl", hash = "sha256:889cf695f84e4ae2c55fbc0cfdaf4c1e729417fa52ab1db0485202ba173e4935", size = 45258, upload-time = "2020-07-11T14:49:58.287Z" }, -] - [[package]] name = "greenlet" version = "3.1.1" @@ -1620,29 +1597,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b5/35/6c4c6fc8774a9e3629cd750dc24a7a4fb090a25ccd5c3246d127b70f9e22/propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043", size = 12101, upload-time = "2025-02-20T19:03:27.202Z" }, ] -[[package]] -name = "protobuf" -version = "6.31.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/f3/b9655a711b32c19720253f6f06326faf90580834e2e83f840472d752bc8b/protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a", size = 441797, upload-time = "2025-05-28T19:25:54.947Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/6f/6ab8e4bf962fd5570d3deaa2d5c38f0a363f57b4501047b5ebeb83ab1125/protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9", size = 423603, upload-time = "2025-05-28T19:25:41.198Z" }, - { url = "https://files.pythonhosted.org/packages/44/3a/b15c4347dd4bf3a1b0ee882f384623e2063bb5cf9fa9d57990a4f7df2fb6/protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447", size = 435283, upload-time = "2025-05-28T19:25:44.275Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c9/b9689a2a250264a84e66c46d8862ba788ee7a641cdca39bccf64f59284b7/protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402", size = 425604, upload-time = "2025-05-28T19:25:45.702Z" }, - { url = "https://files.pythonhosted.org/packages/76/a1/7a5a94032c83375e4fe7e7f56e3976ea6ac90c5e85fac8576409e25c39c3/protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39", size = 322115, upload-time = "2025-05-28T19:25:47.128Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b1/b59d405d64d31999244643d88c45c8241c58f17cc887e73bcb90602327f8/protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6", size = 321070, upload-time = "2025-05-28T19:25:50.036Z" }, - { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, -] - -[[package]] -name = "py" -version = "1.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/ff/fec109ceb715d2a6b4c4a85a61af3b40c723a961e8828319fbcb15b868dc/py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", size = 207796, upload-time = "2021-11-04T17:17:01.377Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -1652,12 +1606,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, ] -[[package]] -name = "pycryptodome" -version = "3.9.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/3a/5bca2cb1648b171afd6b7d29a11c6bca8b305bb75b7e2d78a0f5c61ff95e/pycryptodome-3.9.9.tar.gz", hash = "sha256:910e202a557e1131b1c1b3f17a63914d57aac55cf9fb9b51644962841c3995c4", size = 15488528, upload-time = "2020-11-03T13:15:26.723Z" } - [[package]] name = "pydantic" version = "2.10.6" @@ -1923,19 +1871,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, ] -[[package]] -name = "retry" -version = "0.9.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "decorator" }, - { name = "py" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9d/72/75d0b85443fbc8d9f38d08d2b1b67cc184ce35280e4a3813cda2f445f3a4/retry-0.9.2.tar.gz", hash = "sha256:f8bfa8b99b69c4506d6f5bd3b0aabf77f98cdb17f3c9fc3f5ca820033336fba4", size = 6448, upload-time = "2016-05-11T13:58:51.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/0d/53aea75710af4528a25ed6837d71d117602b01946b307a3912cb3cfcbcba/retry-0.9.2-py2.py3-none-any.whl", hash = "sha256:ccddf89761fa2c726ab29391837d4327f819ea14d244c232a1d24c67a2f98606", size = 7986, upload-time = "2016-05-11T13:58:39.925Z" }, -] - [[package]] name = "rpds-py" version = "0.23.1" @@ -1987,25 +1922,25 @@ wheels = [ name = "ruff" version = "0.11.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632 } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243 }, - { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636 }, - { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624 }, - { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358 }, - { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850 }, - { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787 }, - { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479 }, - { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760 }, - { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747 }, - { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657 }, - { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671 }, - { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135 }, - { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179 }, - { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021 }, - { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 }, - { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285 }, - { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 }, + { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" }, + { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" }, + { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" }, + { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" }, + { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" }, ] [[package]] @@ -2246,21 +2181,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/14/33a3a1352cfa71812a3a21e8c9bfb83f60b0011f5e36f2b1399d51928209/uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4", size = 62315, upload-time = "2024-12-15T13:33:27.467Z" }, ] -[[package]] -name = "volcengine" -version = "1.0.191" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "google" }, - { name = "protobuf" }, - { name = "pycryptodome" }, - { name = "pytz" }, - { name = "requests" }, - { name = "retry" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/d8/0ea9b18f216808af709306084d10369f712b98cb5381381d44115dfa6536/volcengine-1.0.191.tar.gz", hash = "sha256:cf3c2dc118c92a7a47f1ab8a48f4789d47e84d17778a1717e1afe9cbce90c986", size = 356076, upload-time = "2025-06-26T12:25:16.353Z" } - [[package]] name = "watchfiles" version = "1.0.5"