diff --git a/aliyun/log/__init__.py b/aliyun/log/__init__.py index 3075e20..52471f6 100755 --- a/aliyun/log/__init__.py +++ b/aliyun/log/__init__.py @@ -39,6 +39,10 @@ from .rebuild_index_response import * from .deletelogsrequest import * from .deletelogssresponse import * +from .deletelogsv2request import * +from .deletelogsv2response import * +from .updatelogsrequest import * +from .updatelogsresponse import * from .getdeletelogsstatusrequest import * from .getdeletelogsstatusresponse import * from .listdeletelogsstasksrequest import * diff --git a/aliyun/log/__init__.pyi b/aliyun/log/__init__.pyi index d519bd5..8a86130 100644 --- a/aliyun/log/__init__.pyi +++ b/aliyun/log/__init__.pyi @@ -39,6 +39,10 @@ from .proto import LogGroupRaw as LogGroup from .rebuild_index_response import CreateRebuildIndexResponse as CreateRebuildIndexResponse, GetRebuildIndexResponse as GetRebuildIndexResponse from .deletelogsrequest import DeleteLogsRequest as DeleteLogsRequest from .deletelogssresponse import DeleteLogsResponse as DeleteLogsResponse +from .deletelogsv2request import DeleteLogsV2Request as DeleteLogsV2Request +from .deletelogsv2response import DeleteLogsV2Response as DeleteLogsV2Response +from .updatelogsrequest import UpdateLogsRequest as UpdateLogsRequest +from .updatelogsresponse import UpdateLogsResponse as UpdateLogsResponse from .getdeletelogsstatusrequest import GetDeleteLogsStatusRequest as GetDeleteLogsStatusRequest from .getdeletelogsstatusresponse import GetDeleteLogsStatusResponse as GetDeleteLogsStatusResponse from .listdeletelogsstasksrequest import ListDeleteLogsTasksRequest as ListDeleteLogsTasksRequest diff --git a/aliyun/log/deletelogsv2request.py b/aliyun/log/deletelogsv2request.py new file mode 100644 index 0000000..b6cdf68 --- /dev/null +++ b/aliyun/log/deletelogsv2request.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# Copyright (C) Alibaba Cloud Computing +# All rights reserved. + +from .logrequest import LogRequest +from .util import parse_timestamp + + +class DeleteLogsV2Request(LogRequest): + """The request used to delete logs from a logstore. + + :type project: string + :param project: project name + + :type logstore: string + :param logstore: logstore name + + :type fromTime: int/string + :param fromTime: the begin time + + :type toTime: int/string + :param toTime: the end time + + :type query: string + :param query: user defined query + + :type rowId: string + :param rowId: row id of the log + """ + + def __init__(self, project=None, logstore=None, fromTime=None, toTime=None, query=None, rowId=None): + LogRequest.__init__(self, project) + self.logstore = logstore + self.fromTime = parse_timestamp(fromTime) if fromTime is not None else fromTime + self.toTime = parse_timestamp(toTime) if toTime is not None else toTime + self.query = query + self.rowId = rowId + + def get_logstore(self): + """Get logstore name. + + :return: string, logstore name. + """ + return self.logstore if self.logstore else '' + + def set_logstore(self, logstore): + """Set logstore name. + + :type logstore: string + :param logstore: logstore name + """ + self.logstore = logstore + + def get_from(self): + """Get begin time. + + :return: int, begin time + """ + return self.fromTime + + def set_from(self, fromTime): + """Set begin time. + + :type fromTime: int/string + :param fromTime: begin time + """ + self.fromTime = parse_timestamp(fromTime) if fromTime is not None else fromTime + + def get_to(self): + """Get end time. + + :return: int, end time + """ + return self.toTime + + def set_to(self, toTime): + """Set end time. + + :type toTime: int/string + :param toTime: end time + """ + self.toTime = parse_timestamp(toTime) if toTime is not None else toTime + + def get_query(self): + """Get user defined query. + + :return: string, user defined query + """ + return self.query + + def set_query(self, query): + """Set user defined query. + + :type query: string + :param query: user defined query + """ + self.query = query + + def get_row_id(self): + """Get row id. + + :return: string, row id + """ + return self.rowId + + def set_row_id(self, rowId): + """Set row id. + + :type rowId: string + :param rowId: row id + """ + self.rowId = rowId diff --git a/aliyun/log/deletelogsv2request.pyi b/aliyun/log/deletelogsv2request.pyi new file mode 100644 index 0000000..00efe42 --- /dev/null +++ b/aliyun/log/deletelogsv2request.pyi @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +from typing import Optional, Union + +from .logrequest import LogRequest + +class DeleteLogsV2Request(LogRequest): + def __init__(self, project: Optional[str] = ..., logstore: Optional[str] = ..., fromTime: Optional[Union[int, str]] = ..., toTime: Optional[Union[int, str]] = ..., query: Optional[str] = ..., rowId: Optional[str] = ...) -> None: ... + def get_logstore(self) -> str: ... + def set_logstore(self, logstore: str) -> None: ... + def get_from(self) -> Optional[int]: ... + def set_from(self, fromTime: Optional[Union[int, str]]) -> None: ... + def get_to(self) -> Optional[int]: ... + def set_to(self, toTime: Optional[Union[int, str]]) -> None: ... + def get_query(self) -> Optional[str]: ... + def set_query(self, query: Optional[str]) -> None: ... + def get_row_id(self) -> Optional[str]: ... + def set_row_id(self, rowId: Optional[str]) -> None: ... diff --git a/aliyun/log/deletelogsv2response.py b/aliyun/log/deletelogsv2response.py new file mode 100644 index 0000000..b591e88 --- /dev/null +++ b/aliyun/log/deletelogsv2response.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# Copyright (C) Alibaba Cloud Computing +# All rights reserved. + +from .logresponse import LogResponse + + +class DeleteLogsV2Response(LogResponse): + """The response of the DeleteLogsV2 API from log.""" + + def __init__(self, resp, header): + LogResponse.__init__(self, header, resp) + self.affected_rows = resp.get('affected_rows', 0) + + def get_affected_rows(self): + return self.affected_rows + + def log_print(self): + print('DeleteLogsV2Response:') + print('headers:', self.get_all_headers()) + print('affected_rows:', self.affected_rows) diff --git a/aliyun/log/deletelogsv2response.pyi b/aliyun/log/deletelogsv2response.pyi new file mode 100644 index 0000000..21cbccf --- /dev/null +++ b/aliyun/log/deletelogsv2response.pyi @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from typing import Any, Dict + +from .logresponse import LogResponse + +class DeleteLogsV2Response(LogResponse): + affected_rows: int + def __init__(self, resp: Dict[str, Any], header: Dict[str, Any]) -> None: ... + def get_affected_rows(self) -> int: ... + def log_print(self) -> None: ... diff --git a/aliyun/log/logclient.py b/aliyun/log/logclient.py index 9081c99..2e8594b 100644 --- a/aliyun/log/logclient.py +++ b/aliyun/log/logclient.py @@ -21,12 +21,16 @@ from itertools import cycle from .consumer_group_request import * from .consumer_group_response import * -from .getlogsrequest import * -from .cursor_response import GetCursorResponse -from .cursor_time_response import GetCursorTimeResponse -from .gethistogramsresponse import GetHistogramsResponse -from .deletelogssresponse import DeleteLogsResponse -from .getlogsresponse import GetLogsResponse +from .getlogsrequest import * +from .cursor_response import GetCursorResponse +from .cursor_time_response import GetCursorTimeResponse +from .gethistogramsresponse import GetHistogramsResponse +from .deletelogssresponse import DeleteLogsResponse +from .deletelogsv2request import DeleteLogsV2Request +from .deletelogsv2response import DeleteLogsV2Response +from .updatelogsrequest import UpdateLogsRequest +from .updatelogsresponse import UpdateLogsResponse +from .getlogsresponse import GetLogsResponse from .getdeletelogsstatusresponse import GetDeleteLogsStatusResponse from .listdeletelogsstasksresponse import ListDeleteLogsTasksResponse from .getcontextlogsresponse import GetContextLogsResponse @@ -566,9 +570,9 @@ def get_histograms(self, request): (resp, header) = self._send("GET", project, None, resource, params, headers) return GetHistogramsResponse(resp, header) - def delete_logs(self, request): - """ delete logs of requested query from log service. - Unsuccessful operation will cause an LogException. + def delete_logs(self, request): + """ delete logs of requested query from log service. + Unsuccessful operation will cause an LogException. :type request: DeleteLogsRequest :param request: the DeleteLogsRequest request parameters class. @@ -595,12 +599,159 @@ def delete_logs(self, request): resource = "/logstores/" + logstore + "/deletelogtasks" body_str = six.b(json.dumps(params)) headers["x-log-bodyrawsize"] = str(len(body_str)) - (resp, header) = self._send("POST", project, body_str, resource, None, headers) - return DeleteLogsResponse(resp, header) - - def get_delete_logs_status(self, request): - """ Get get_delete_logs_status of requested logstore from log service. - Unsuccessful operation will cause an LogException. + (resp, header) = self._send("POST", project, body_str, resource, None, headers) + return DeleteLogsResponse(resp, header) + + @staticmethod + def _log_item_to_update_data(log_item): + if log_item is None: + return None + if hasattr(log_item, 'get_contents'): + return json.dumps(dict(log_item.get_contents())) + if isinstance(log_item, dict): + return json.dumps(log_item) + return log_item + + @staticmethod + def _choose_row_id(rowid, row_id): + return rowid if rowid is not None else row_id + + def delete_logs_v2(self, request=None, project=None, logstore=None, from_time=None, to_time=None, + query=None, rowid=None, row_id=None): + """Delete logs from a logstore. + + Unsuccessful operation will cause an LogException. + + :type request: DeleteLogsV2Request + :param request: the DeleteLogsV2Request request parameters class. + + :type project: string + :param project: project name + + :type logstore: string + :param logstore: logstore name + + :type from_time: int/string + :param from_time: the begin time + + :type to_time: int/string + :param to_time: the end time + + :type query: string + :param query: user defined query + + :type rowid: string + :param rowid: row id of the log + + :type row_id: string + :param row_id: row id of the log + + :return: DeleteLogsV2Response + + :raise: LogException + """ + if request is None: + request = DeleteLogsV2Request(project, logstore, from_time, to_time, query, + self._choose_row_id(rowid, row_id)) + + body = {} + if request.get_from() is not None: + body['from'] = request.get_from() + if request.get_to() is not None: + body['to'] = request.get_to() + if request.get_query() is not None: + body['query'] = request.get_query() + if request.get_row_id() is not None: + body['rowId'] = request.get_row_id() + + body_str = six.b(json.dumps(body)) + headers = { + 'Content-Type': 'application/json', + 'x-log-bodyrawsize': str(len(body_str)) + } + logstore = request.get_logstore() + project = request.get_project() + resource = "/logstores/" + logstore + "/deletelogs" + (resp, header) = self._send("POST", project, body_str, resource, None, headers) + return DeleteLogsV2Response(resp, header) + + def update_logs(self, request=None, project=None, logstore=None, from_time=None, to_time=None, + query=None, rowid=None, row_id=None, log_item=None, update_mode=None, data=None): + """Update logs in a logstore. + + Unsuccessful operation will cause an LogException. + + :type request: UpdateLogsRequest + :param request: the UpdateLogsRequest request parameters class. + + :type project: string + :param project: project name + + :type logstore: string + :param logstore: logstore name + + :type from_time: int/string + :param from_time: the begin time + + :type to_time: int/string + :param to_time: the end time + + :type query: string + :param query: user defined query + + :type rowid: string + :param rowid: row id of the log + + :type row_id: string + :param row_id: row id of the log + + :type log_item: LogItem/dict + :param log_item: fields to update + + :type update_mode: string + :param update_mode: update mode + + :type data: string + :param data: update data + + :return: UpdateLogsResponse + + :raise: LogException + """ + if request is None: + if data is None: + data = self._log_item_to_update_data(log_item) + request = UpdateLogsRequest(project, logstore, from_time, to_time, query, + self._choose_row_id(rowid, row_id), update_mode, data) + + body = {} + if request.get_from() is not None: + body['from'] = request.get_from() + if request.get_to() is not None: + body['to'] = request.get_to() + if request.get_query() is not None: + body['query'] = request.get_query() + if request.get_row_id() is not None: + body['rowId'] = request.get_row_id() + if request.get_update_mode() is not None: + body['updateMode'] = request.get_update_mode() + if request.get_data() is not None: + body['data'] = request.get_data() + + body_str = six.b(json.dumps(body)) + headers = { + 'Content-Type': 'application/json', + 'x-log-bodyrawsize': str(len(body_str)) + } + logstore = request.get_logstore() + project = request.get_project() + resource = "/logstores/" + logstore + "/updatelogs" + (resp, header) = self._send("POST", project, body_str, resource, None, headers) + return UpdateLogsResponse(resp, header) + + def get_delete_logs_status(self, request): + """ Get get_delete_logs_status of requested logstore from log service. + Unsuccessful operation will cause an LogException. :type request: GetDeleteLogsStatusRequest :param request: the GetDeleteLogsStatusRequest request parameters class. @@ -1470,9 +1621,9 @@ def pull_log_dump(self, project_name, logstore_name, from_time, to_time, file_pa batch_size=batch_size, compress=compress, encodings=encodings, shard_list=shard_list, no_escape=no_escape, query=query, processor=processor) - def create_logstore(self, project_name, logstore_name, - ttl=30, - shard_count=2, + def create_logstore(self, project_name, logstore_name, + ttl=30, + shard_count=2, enable_tracking=False, append_meta=False, auto_split=True, @@ -1480,10 +1631,11 @@ def create_logstore(self, project_name, logstore_name, preserve_storage=False, encrypt_conf=None, telemetry_type='', - hot_ttl=-1, - mode = None, - infrequent_access_ttl=-1 - ): + hot_ttl=-1, + mode = None, + infrequent_access_ttl=-1, + enable_modify=False + ): """ create log store Unsuccessful operation will cause an LogException. @@ -1535,12 +1687,15 @@ def create_logstore(self, project_name, logstore_name, :type infrequent_access_ttl: int :param infrequent_access_ttl: infrequent access storage time - :type hot_ttl: int - :param hot_ttl: the life cycle of hot storage,[0-hot_ttl]is hot storage, (hot_ttl-ttl] is warm storage, if hot_ttl=-1, it means [0-ttl]is all hot storage - - :return: CreateLogStoreResponse - - :raise: LogException + :type hot_ttl: int + :param hot_ttl: the life cycle of hot storage,[0-hot_ttl]is hot storage, (hot_ttl-ttl] is warm storage, if hot_ttl=-1, it means [0-ttl]is all hot storage + + :type enable_modify: bool + :param enable_modify: enable log modification and deletion, default is False + + :return: CreateLogStoreResponse + + :raise: LogException """ if preserve_storage: ttl = 3650 @@ -1555,11 +1710,13 @@ def create_logstore(self, project_name, logstore_name, "enable_tracking": enable_tracking, "autoSplit": auto_split, "maxSplitShard": max_split_shard, - "appendMeta": append_meta, - "telemetryType": telemetry_type - } - if hot_ttl !=-1: - body['hot_ttl'] = hot_ttl + "appendMeta": append_meta, + "telemetryType": telemetry_type + } + if enable_modify: + body["enableModify"] = enable_modify + if hot_ttl !=-1: + body['hot_ttl'] = hot_ttl if encrypt_conf != None: body["encrypt_conf"] = encrypt_conf if mode != None: diff --git a/aliyun/log/logclient.pyi b/aliyun/log/logclient.pyi index 5abea38..fd3bdb3 100644 --- a/aliyun/log/logclient.pyi +++ b/aliyun/log/logclient.pyi @@ -9,6 +9,8 @@ from .cursor_time_response import GetCursorTimeResponse from .delete_async_sql_request import DeleteAsyncSqlRequest from .deletelogsrequest import DeleteLogsRequest from .deletelogssresponse import DeleteLogsResponse +from .deletelogsv2request import DeleteLogsV2Request +from .deletelogsv2response import DeleteLogsV2Response from .etl_config_response import CreateEtlResponse, DeleteEtlResponse, GetEtlResponse, ListEtlsResponse, StartEtlResponse, StopEtlResponse, UpdateEtlResponse from .export_response import CreateExportResponse, DeleteExportResponse, GetExportResponse, ListExportResponse, UpdateExportResponse from .external_store_config import ExternalStoreConfigBase @@ -31,6 +33,7 @@ from .listlogstoresrequest import ListLogstoresRequest from .listlogstoresresponse import ListLogstoresResponse from .listtopicsrequest import ListTopicsRequest from .listtopicsresponse import ListTopicsResponse +from .logitem import LogItem from .logclient_operator import ResourceUsageResponse, copy_project, list_more, query_more, pull_log_dump, copy_logstore, copy_data, get_resource_usage, arrange_shard, transform_data, copy_dashboard, copy_alert from .logexception import LogException from .logresponse import LogResponse @@ -61,6 +64,8 @@ from .store_view import StoreView from .store_view_response import CreateStoreViewResponse, DeleteStoreViewResponse, GetStoreViewResponse, ListStoreViewsResponse, UpdateStoreViewResponse from .substore_config_response import CreateMetricsStoreResponse, CreateSubStoreResponse, DeleteSubStoreResponse, GetSubStoreResponse, GetSubStoreTTLResponse, ListSubStoreResponse, UpdateSubStoreResponse, UpdateSubStoreTTLResponse from .submit_async_sql_request import SubmitAsyncSqlRequest +from .updatelogsrequest import UpdateLogsRequest +from .updatelogsresponse import UpdateLogsResponse from .tag_response import GetResourceTagsResponse from .topostore_params import Topostore, TopostoreNode, TopostoreRelation from .topostore_response import CreateTopostoreNodeResponse, CreateTopostoreRelationResponse, CreateTopostoreResponse, DeleteTopostoreNodeResponse, DeleteTopostoreRelationResponse, DeleteTopostoreResponse, GetTopostoreNodeResponse, GetTopostoreRelationResponse, GetTopostoreResponse, ListTopostoreNodesResponse, ListTopostoreRelationsResponse, ListTopostoresResponse, UpdateTopostoreNodeResponse, UpdateTopostoreRelationResponse, UpdateTopostoreResponse, UpsertTopostoreNodeResponse, UpsertTopostoreRelationResponse @@ -82,6 +87,8 @@ class LogClient(object): def list_topics(self, request: ListTopicsRequest) -> ListTopicsResponse: ... def get_histograms(self, request: GetHistogramsRequest) -> GetHistogramsResponse: ... def delete_logs(self, request: DeleteLogsRequest) -> DeleteLogsResponse: ... + def delete_logs_v2(self, request: Optional[DeleteLogsV2Request] = ..., project: Optional[str] = ..., logstore: Optional[str] = ..., from_time: Optional[Union[int, str]] = ..., to_time: Optional[Union[int, str]] = ..., query: Optional[str] = ..., rowid: Optional[str] = ..., row_id: Optional[str] = ...) -> DeleteLogsV2Response: ... + def update_logs(self, request: Optional[UpdateLogsRequest] = ..., project: Optional[str] = ..., logstore: Optional[str] = ..., from_time: Optional[Union[int, str]] = ..., to_time: Optional[Union[int, str]] = ..., query: Optional[str] = ..., rowid: Optional[str] = ..., row_id: Optional[str] = ..., log_item: Optional[Union[LogItem, Dict[str, Any], str]] = ..., update_mode: Optional[str] = ..., data: Optional[str] = ...) -> UpdateLogsResponse: ... def get_delete_logs_status(self, request: GetDeleteLogsStatusRequest) -> GetDeleteLogsStatusResponse: ... def list_delete_logs_tasks(self, request: ListDeleteLogsTasksRequest) -> ListDeleteLogsTasksResponse: ... def get_log(self, project: str, logstore: str, from_time: Union[int, str], to_time: Union[int, str], topic: Optional[str] = ..., query: Optional[str] = ..., reverse: bool = ..., offset: int = ..., size: int = ..., power_sql: bool = ..., scan: bool = ..., forward: bool = ..., accurate_query: bool = ..., from_time_nano_part: int = ..., to_time_nano_part: int = ...) -> GetLogsResponse: ... @@ -103,7 +110,7 @@ class LogClient(object): def pull_logs(self, project_name: str, logstore_name: str, shard_id: int, cursor: str, count: Optional[int] = ..., end_cursor: Optional[str] = ..., compress: Optional[bool] = ..., query: Optional[str] = ..., accept_compress_type: Optional[str] = ..., processor: Optional[str] = ...) -> PullLogResponse: ... def pull_log(self, project_name: str, logstore_name: str, shard_id: int, from_time: Union[int, str], to_time: Union[int, str], batch_size: Optional[int] = ..., compress: Optional[bool] = ..., query: Optional[str] = ..., accept_compress_type: Optional[str] = ..., processor: Optional[str] = ...) -> Iterator[PullLogResponse]: ... def pull_log_dump(self, project_name: str, logstore_name: str, from_time: Union[int, str], to_time: Union[int, str], file_path: str, batch_size: Optional[int] = ..., compress: Optional[bool] = ..., encodings: Optional[List[str]] = ..., shard_list: Optional[Union[str, List[str]]] = ..., no_escape: Optional[bool] = ..., query: Optional[str] = ..., processor: Optional[str] = ...) -> LogResponse: ... - 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 = ...) -> CreateLogStoreResponse: ... + 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 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: ... diff --git a/aliyun/log/updatelogsrequest.py b/aliyun/log/updatelogsrequest.py new file mode 100644 index 0000000..821f9c5 --- /dev/null +++ b/aliyun/log/updatelogsrequest.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# Copyright (C) Alibaba Cloud Computing +# All rights reserved. + +from .logrequest import LogRequest +from .util import parse_timestamp + + +class UpdateLogsRequest(LogRequest): + """The request used to update logs in a logstore.""" + + def __init__(self, project=None, logstore=None, fromTime=None, toTime=None, + query=None, rowId=None, updateMode=None, data=None): + LogRequest.__init__(self, project) + self.logstore = logstore + self.fromTime = parse_timestamp(fromTime) if fromTime is not None else fromTime + self.toTime = parse_timestamp(toTime) if toTime is not None else toTime + self.query = query + self.rowId = rowId + self.updateMode = updateMode + self.data = data + + def get_logstore(self): + """Get logstore name. + + :return: string, logstore name. + """ + return self.logstore if self.logstore else '' + + def set_logstore(self, logstore): + """Set logstore name. + + :type logstore: string + :param logstore: logstore name + """ + self.logstore = logstore + + def get_from(self): + """Get begin time. + + :return: int, begin time + """ + return self.fromTime + + def set_from(self, fromTime): + """Set begin time. + + :type fromTime: int/string + :param fromTime: begin time + """ + self.fromTime = parse_timestamp(fromTime) if fromTime is not None else fromTime + + def get_to(self): + """Get end time. + + :return: int, end time + """ + return self.toTime + + def set_to(self, toTime): + """Set end time. + + :type toTime: int/string + :param toTime: end time + """ + self.toTime = parse_timestamp(toTime) if toTime is not None else toTime + + def get_query(self): + """Get user defined query. + + :return: string, user defined query + """ + return self.query + + def set_query(self, query): + """Set user defined query. + + :type query: string + :param query: user defined query + """ + self.query = query + + def get_row_id(self): + """Get row id. + + :return: string, row id + """ + return self.rowId + + def set_row_id(self, rowId): + """Set row id. + + :type rowId: string + :param rowId: row id + """ + self.rowId = rowId + + def get_update_mode(self): + """Get update mode. + + :return: string, update mode + """ + return self.updateMode + + def set_update_mode(self, updateMode): + """Set update mode. + + :type updateMode: string + :param updateMode: update mode + """ + self.updateMode = updateMode + + def get_data(self): + """Get update data. + + :return: string, update data + """ + return self.data + + def set_data(self, data): + """Set update data. + + :type data: string + :param data: update data + """ + self.data = data diff --git a/aliyun/log/updatelogsrequest.pyi b/aliyun/log/updatelogsrequest.pyi new file mode 100644 index 0000000..22a7323 --- /dev/null +++ b/aliyun/log/updatelogsrequest.pyi @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +from typing import Optional, Union + +from .logrequest import LogRequest + +class UpdateLogsRequest(LogRequest): + def __init__(self, project: Optional[str] = ..., logstore: Optional[str] = ..., fromTime: Optional[Union[int, str]] = ..., toTime: Optional[Union[int, str]] = ..., query: Optional[str] = ..., rowId: Optional[str] = ..., updateMode: Optional[str] = ..., data: Optional[str] = ...) -> None: ... + def get_logstore(self) -> str: ... + def set_logstore(self, logstore: str) -> None: ... + def get_from(self) -> Optional[int]: ... + def set_from(self, fromTime: Optional[Union[int, str]]) -> None: ... + def get_to(self) -> Optional[int]: ... + def set_to(self, toTime: Optional[Union[int, str]]) -> None: ... + def get_query(self) -> Optional[str]: ... + def set_query(self, query: Optional[str]) -> None: ... + def get_row_id(self) -> Optional[str]: ... + def set_row_id(self, rowId: Optional[str]) -> None: ... + def get_update_mode(self) -> Optional[str]: ... + def set_update_mode(self, updateMode: Optional[str]) -> None: ... + def get_data(self) -> Optional[str]: ... + def set_data(self, data: Optional[str]) -> None: ... diff --git a/aliyun/log/updatelogsresponse.py b/aliyun/log/updatelogsresponse.py new file mode 100644 index 0000000..f474919 --- /dev/null +++ b/aliyun/log/updatelogsresponse.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# Copyright (C) Alibaba Cloud Computing +# All rights reserved. + +from .logresponse import LogResponse + + +class UpdateLogsResponse(LogResponse): + """The response of the UpdateLogs API from log.""" + + def __init__(self, resp, header): + LogResponse.__init__(self, header, resp) + self.affected_rows = resp.get('affected_rows', 0) + + def get_affected_rows(self): + return self.affected_rows + + def log_print(self): + print('UpdateLogsResponse:') + print('headers:', self.get_all_headers()) + print('affected_rows:', self.affected_rows) diff --git a/aliyun/log/updatelogsresponse.pyi b/aliyun/log/updatelogsresponse.pyi new file mode 100644 index 0000000..29c2b7b --- /dev/null +++ b/aliyun/log/updatelogsresponse.pyi @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +from typing import Any, Dict + +from .logresponse import LogResponse + +class UpdateLogsResponse(LogResponse): + affected_rows: int + def __init__(self, resp: Dict[str, Any], header: Dict[str, Any]) -> None: ... + def get_affected_rows(self) -> int: ... + def log_print(self) -> None: ... diff --git a/tests/unit/test_logclient_mock.py b/tests/unit/test_logclient_mock.py index 8ed17a0..b51fd8d 100644 --- a/tests/unit/test_logclient_mock.py +++ b/tests/unit/test_logclient_mock.py @@ -13,7 +13,15 @@ import pytest import responses -from aliyun.log import GetLogsRequest, LogClient, LogException, LogItem, PutLogsRequest +from aliyun.log import ( + DeleteLogsV2Request, + GetLogsRequest, + LogClient, + LogException, + LogItem, + PutLogsRequest, + UpdateLogsRequest, +) from tests._helpers.fakes import error_response, make_client, mock_sls_response @@ -126,3 +134,273 @@ def test_error_response_raises_logexception(): with pytest.raises(LogException) as excinfo: client.get_logs(req) assert excinfo.value.get_error_code() == "ParameterInvalid" + + +@responses.activate +def test_delete_logs_v2_posts_backend_body_and_parses_affected_rows(): + """DeleteLogsV2 posts to /deletelogs and parses affected_rows.""" + client = make_client(endpoint="cn-mock.example.com", project="mock-proj") + + captured = {} + + def request_callback(request): + captured["headers"] = dict(request.headers) + captured["body"] = json.loads(request.body.decode("utf-8")) + return ( + 200, + { + "x-log-requestid": "mock-request-id", + "Content-Type": "application/json", + }, + json.dumps({"affected_rows": 3}), + ) + + responses.add_callback( + responses.POST, + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores/store-1/deletelogs$"), + callback=request_callback, + ) + + req = DeleteLogsV2Request( + project="mock-proj", + logstore="store-1", + fromTime=1700000000, + toTime=1700000100, + query="level:error", + rowId="row-1", + ) + resp = client.delete_logs_v2(req) + + assert captured["headers"].get("Content-Type") == "application/json" + assert captured["body"] == { + "from": 1700000000, + "to": 1700000100, + "query": "level:error", + "rowId": "row-1", + } + assert resp.get_affected_rows() == 3 + assert resp.affected_rows == 3 + + +@responses.activate +def test_delete_logs_v2_accepts_documented_keyword_arguments(): + """DeleteLogsV2 supports the documented project/logstore/rowid call shape.""" + 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")) + return ( + 200, + { + "x-log-requestid": "mock-request-id", + "Content-Type": "application/json", + }, + json.dumps({"affected_rows": 1}), + ) + + responses.add_callback( + responses.POST, + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores/store-1/deletelogs$"), + callback=request_callback, + ) + + resp = client.delete_logs_v2( + project="mock-proj", + logstore="store-1", + rowid="row-1", + ) + + assert captured["body"] == {"rowId": "row-1"} + assert resp.affected_rows == 1 + + +@responses.activate +def test_update_logs_posts_backend_body_and_parses_affected_rows(): + """UpdateLogs posts to /updatelogs and leaves data as a string.""" + 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")) + return ( + 200, + { + "x-log-requestid": "mock-request-id", + "Content-Type": "application/json", + }, + json.dumps({"affected_rows": 2}), + ) + + responses.add_callback( + responses.POST, + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores/store-1/updatelogs$"), + callback=request_callback, + ) + + data = '{"level":"warning"}' + req = UpdateLogsRequest( + project="mock-proj", + logstore="store-1", + fromTime=1700000000, + toTime=1700000100, + query="level:error", + rowId="row-1", + updateMode="replace", + data=data, + ) + resp = client.update_logs(req) + + assert captured["body"] == { + "from": 1700000000, + "to": 1700000100, + "query": "level:error", + "rowId": "row-1", + "updateMode": "replace", + "data": data, + } + assert resp.get_affected_rows() == 2 + + +@responses.activate +def test_update_logs_accepts_documented_log_item_keyword_arguments(): + """UpdateLogs supports the documented LogItem keyword call shape.""" + 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")) + return ( + 200, + { + "x-log-requestid": "mock-request-id", + "Content-Type": "application/json", + }, + json.dumps({"affected_rows": 1}), + ) + + responses.add_callback( + responses.POST, + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores/store-1/updatelogs$"), + callback=request_callback, + ) + + resp = client.update_logs( + project="mock-proj", + logstore="store-1", + rowid="row-1", + log_item=LogItem(contents=[("status", "REFUNDED")]), + ) + + assert captured["body"] == { + "rowId": "row-1", + "data": '{"status": "REFUNDED"}', + } + assert resp.affected_rows == 1 + + +@responses.activate +def test_update_logs_accepts_documented_dict_keyword_arguments(): + """UpdateLogs supports dict log_item and row_id aliases from the docs.""" + 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")) + return ( + 200, + { + "x-log-requestid": "mock-request-id", + "Content-Type": "application/json", + }, + json.dumps({"affected_rows": 2}), + ) + + responses.add_callback( + responses.POST, + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores/store-1/updatelogs$"), + callback=request_callback, + ) + + resp = client.update_logs( + project="mock-proj", + logstore="store-1", + from_time=1700000000, + to_time=1700000100, + query='order_id: 12345 and status: "PENDING"', + row_id="", + log_item={"status": "REFUNDED", "refund_at": "2026-05-25T10:00:00Z"}, + ) + + assert captured["body"] == { + "from": 1700000000, + "to": 1700000100, + "query": 'order_id: 12345 and status: "PENDING"', + "rowId": "", + "data": '{"status": "REFUNDED", "refund_at": "2026-05-25T10:00:00Z"}', + } + assert resp.affected_rows == 2 + + +@responses.activate +def test_update_logs_error_response_raises_logexception(): + """A server error envelope from UpdateLogs is converted to LogException.""" + client = make_client(endpoint="cn-mock.example.com", project="mock-proj") + + mock_sls_response( + responses, + "POST", + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores/store-1/updatelogs$"), + status=400, + body=error_response("ParameterInvalid", "bad update request"), + ) + + req = UpdateLogsRequest( + project="mock-proj", + logstore="store-1", + fromTime=1700000000, + toTime=1700000100, + query="*", + ) + with pytest.raises(LogException) as excinfo: + client.update_logs(req) + assert excinfo.value.get_error_code() == "ParameterInvalid" + + +@responses.activate +def test_create_logstore_supports_enable_modify(): + """CreateLogstore includes enableModify when requested by docs.""" + 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")) + return ( + 200, + { + "x-log-requestid": "mock-request-id", + "Content-Type": "application/json", + }, + "{}", + ) + + responses.add_callback( + responses.POST, + re.compile(r"https?://mock-proj\.cn-mock\.example\.com.*?/logstores$"), + callback=request_callback, + ) + + client.create_logstore( + project_name="mock-proj", + logstore_name="store-1", + ttl=30, + shard_count=1, + enable_modify=True, + ) + + assert captured["body"]["enableModify"] is True