From 2320c7e0573f9e36e77a2b9d620ca813d33b54f3 Mon Sep 17 00:00:00 2001 From: "shuizhao.gh" Date: Wed, 12 Aug 2026 14:59:16 +0800 Subject: [PATCH] feat(logstore): support enable modify --- aliyun/log/logclient.py | 37 +++++++++++++--- aliyun/log/logclient.pyi | 1 + aliyun/log/logstore_config_response.py | 5 +++ aliyun/log/logstore_config_response.pyi | 41 ++++++++++++++++++ aliyun/log/version.py | 2 +- tests/unit/test_logclient_mock.py | 57 +++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 aliyun/log/logstore_config_response.pyi diff --git a/aliyun/log/logclient.py b/aliyun/log/logclient.py index 04432686..f78c0c38 100644 --- a/aliyun/log/logclient.py +++ b/aliyun/log/logclient.py @@ -1763,7 +1763,7 @@ def delete_logstore(self, project_name, logstore_name): (resp, header) = self._send("DELETE", project_name, None, resource, params, headers) return DeleteLogStoreResponse(header, resp) - def get_logstore(self, project_name, logstore_name): + def get_logstore(self, project_name, logstore_name): """ get the logstore meta info Unsuccessful operation will cause an LogException. @@ -1781,10 +1781,37 @@ def get_logstore(self, project_name, logstore_name): headers = {} params = {} resource = "/logstores/" + logstore_name - (resp, header) = self._send("GET", project_name, None, resource, params, headers) - return GetLogStoreResponse(resp, header) - - def update_logstore(self, project_name, logstore_name, ttl=None, enable_tracking=None, shard_count=None, + (resp, header) = self._send("GET", project_name, None, resource, params, headers) + return GetLogStoreResponse(resp, header) + + def enable_logstore_modify(self, project_name, logstore_name): + """Enable log modification and deletion for an existing logstore. + + The conversion is asynchronous; a successful response only means that + the enable request was accepted. + + :type project_name: string + :param project_name: the project name + + :type logstore_name: string + :param logstore_name: the logstore name + + :return: LogResponse + + :raise: LogException + """ + body_str = six.b(json.dumps({"enabled": True})) + headers = { + "x-log-bodyrawsize": str(len(body_str)), + "Content-Type": "application/json", + } + resource = "/logstores/" + logstore_name + "/modification" + (resp, header) = self._send( + "PUT", project_name, body_str, resource, {}, headers + ) + return LogResponse(header, resp) + + def update_logstore(self, project_name, logstore_name, ttl=None, enable_tracking=None, shard_count=None, append_meta=None, auto_split=None, max_split_shard=None, diff --git a/aliyun/log/logclient.pyi b/aliyun/log/logclient.pyi index fd3bdb3c..fa0e78a6 100644 --- a/aliyun/log/logclient.pyi +++ b/aliyun/log/logclient.pyi @@ -113,6 +113,7 @@ class LogClient(object): def create_logstore(self, project_name: str, logstore_name: str, ttl: int = ..., shard_count: int = ..., enable_tracking: bool = ..., append_meta: bool = ..., auto_split: bool = ..., max_split_shard: int = ..., preserve_storage: bool = ..., encrypt_conf: Optional[Dict[str, Any]] = ..., telemetry_type: str = ..., hot_ttl: int = ..., mode: Optional[str] = ..., infrequent_access_ttl: int = ..., enable_modify: bool = ...) -> CreateLogStoreResponse: ... def delete_logstore(self, project_name: str, logstore_name: str) -> DeleteLogStoreResponse: ... def get_logstore(self, project_name: str, logstore_name: str) -> GetLogStoreResponse: ... + def enable_logstore_modify(self, project_name: str, logstore_name: str) -> LogResponse: ... def update_logstore(self, project_name: str, logstore_name: str, ttl: Optional[int] = ..., enable_tracking: Optional[bool] = ..., shard_count: Optional[int] = ..., append_meta: Optional[bool] = ..., auto_split: Optional[bool] = ..., max_split_shard: Optional[int] = ..., preserve_storage: Optional[bool] = ..., encrypt_conf: Optional[Dict[str, Any]] = ..., hot_ttl: int = ..., mode: Optional[str] = ..., telemetry_type: Optional[str] = ..., infrequent_access_ttl: int = ...) -> UpdateLogStoreResponse: ... def list_logstore(self, project_name: str, logstore_name_pattern: Optional[str] = ..., offset: int = ..., size: int = ...) -> ListLogStoreResponse: ... def get_logstore_metering_mode(self, project_name: str, logstore_name: str) -> GetLogStoreMeteringModeResponse: ... diff --git a/aliyun/log/logstore_config_response.py b/aliyun/log/logstore_config_response.py index 7413bbee..90eef3ce 100755 --- a/aliyun/log/logstore_config_response.py +++ b/aliyun/log/logstore_config_response.py @@ -57,6 +57,7 @@ def __init__(self, resp, header): self.ttl = int(resp["ttl"]) self.shard_count = int(resp["shardCount"]) self.enable_tracking = bool(resp["enable_tracking"]) + self.enable_modify = bool(resp.get("enableModify", False)) self.append_meta = bool(resp["appendMeta"]) self.auto_split = bool(resp["autoSplit"]) self.max_split_shard = int(resp["maxSplitShard"]) @@ -100,6 +101,10 @@ def get_enable_tracking(self): """ return self.enable_tracking + def get_enable_modify(self): + """Return whether log modification and deletion are enabled.""" + return self.enable_modify + def get_encrypt_conf(self): """ diff --git a/aliyun/log/logstore_config_response.pyi b/aliyun/log/logstore_config_response.pyi new file mode 100644 index 00000000..a715a7af --- /dev/null +++ b/aliyun/log/logstore_config_response.pyi @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +from typing import Any, Dict, List + +from .logresponse import LogResponse + +class CreateLogStoreResponse(LogResponse): + def __init__(self, header: Dict[str, Any], resp: Any = ...) -> None: ... + def log_print(self) -> None: ... + +class DeleteLogStoreResponse(LogResponse): + def __init__(self, header: Dict[str, Any], resp: Any = ...) -> None: ... + def log_print(self) -> None: ... + +class GetLogStoreResponse(LogResponse): + enable_modify: bool + def __init__(self, resp: Dict[str, Any], header: Dict[str, Any]) -> None: ... + def get_shard_count(self) -> int: ... + def get_ttl(self) -> int: ... + def get_hot_ttl(self) -> int: ... + def get_enable_tracking(self) -> bool: ... + def get_enable_modify(self) -> bool: ... + def get_encrypt_conf(self) -> Any: ... + def log_print(self) -> None: ... + +class UpdateLogStoreResponse(LogResponse): + def __init__(self, header: Dict[str, Any], resp: Any = ...) -> None: ... + def log_print(self) -> None: ... + +class ListLogStoreResponse(LogResponse): + def __init__(self, resp: Dict[str, Any], header: Dict[str, Any]) -> None: ... + def get_logstores(self) -> List[str]: ... + def get_count(self) -> int: ... + def get_logstores_count(self) -> int: ... + def get_logstores_total(self) -> int: ... + def get_total(self) -> int: ... + def log_print(self) -> None: ... + def merge(self, response: "ListLogStoreResponse") -> "ListLogStoreResponse": ... + @property + def total(self) -> int: ... + @property + def logstores(self) -> List[str]: ... diff --git a/aliyun/log/version.py b/aliyun/log/version.py index 1b7119f8..d444b64f 100644 --- a/aliyun/log/version.py +++ b/aliyun/log/version.py @@ -1,4 +1,4 @@ -__version__ = '0.9.48' +__version__ = '0.9.49' import sys OS_VERSION = str(sys.platform) diff --git a/tests/unit/test_logclient_mock.py b/tests/unit/test_logclient_mock.py index 8e2275c7..291b855e 100644 --- a/tests/unit/test_logclient_mock.py +++ b/tests/unit/test_logclient_mock.py @@ -448,3 +448,60 @@ def request_callback(request): ) assert captured["body"]["enableModify"] is True + + +@responses.activate +def test_enable_logstore_modify_sends_dedicated_request(): + client = make_client(endpoint="cn-mock.example.com", project="mock-proj") + + captured = {} + + def request_callback(request): + captured["body"] = json.loads(request.body.decode("utf-8")) + captured["headers"] = request.headers + return ( + 200, + {"x-log-requestid": "mock-request-id"}, + "{}", + ) + + responses.add_callback( + responses.PUT, + re.compile( + r"https?://mock-proj\.cn-mock\.example\.com.*?" + r"/logstores/store-1/modification$" + ), + callback=request_callback, + ) + + response = client.enable_logstore_modify("mock-proj", "store-1") + + assert captured["body"] == {"enabled": True} + assert captured["headers"]["Content-Type"] == "application/json" + assert int(captured["headers"]["x-log-bodyrawsize"]) == len( + json.dumps({"enabled": True}).encode("utf-8") + ) + assert response.get_request_id() == "mock-request-id" + + +def test_get_logstore_response_exposes_enable_modify(): + from aliyun.log.logstore_config_response import GetLogStoreResponse + + body = { + "logstoreName": "store-1", + "ttl": 30, + "shardCount": 1, + "enable_tracking": False, + "appendMeta": False, + "autoSplit": True, + "maxSplitShard": 64, + "enableModify": True, + } + + response = GetLogStoreResponse(body, {}) + + assert response.enable_modify is True + assert response.get_enable_modify() is True + + body.pop("enableModify") + assert GetLogStoreResponse(body, {}).get_enable_modify() is False