diff --git a/src/spaceone/inventory_v2/error/region.py b/src/spaceone/inventory_v2/error/region.py index 8499bee..ef8c36d 100644 --- a/src/spaceone/inventory_v2/error/region.py +++ b/src/spaceone/inventory_v2/error/region.py @@ -6,4 +6,4 @@ class ERROR_NOT_FOUND_USER_IN_REGION(ERROR_BASE): class ERROR_ALREADY_EXIST_USER_IN_REGION(ERROR_BASE): - _message = 'A user "{user_id}" is already exist in region ({region_id}).' \ No newline at end of file + _message = 'A user "{user_id}" is already exist in region ({region_id}).' diff --git a/src/spaceone/inventory_v2/info/__init__.py b/src/spaceone/inventory_v2/info/__init__.py deleted file mode 100644 index 96f2f5f..0000000 --- a/src/spaceone/inventory_v2/info/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from spaceone.inventory_v2.info.common_info import * -from spaceone.inventory_v2.info.region_info import * \ No newline at end of file diff --git a/src/spaceone/inventory_v2/info/common_info.py b/src/spaceone/inventory_v2/info/common_info.py deleted file mode 100644 index 2a2b7dc..0000000 --- a/src/spaceone/inventory_v2/info/common_info.py +++ /dev/null @@ -1,20 +0,0 @@ -from google.protobuf.empty_pb2 import Empty -from spaceone.core.pygrpc.message_type import * - -__all__ = ['EmptyInfo', 'StatisticsInfo', 'AnalyzeInfo', 'ExportInfo'] - - -def EmptyInfo(): - return Empty() - - -def StatisticsInfo(result): - return change_struct_type(result) - - -def AnalyzeInfo(result): - return change_struct_type(result) - - -def ExportInfo(result): - return change_struct_type(result) diff --git a/src/spaceone/inventory_v2/info/region_info.py b/src/spaceone/inventory_v2/info/region_info.py deleted file mode 100644 index 0c0d77f..0000000 --- a/src/spaceone/inventory_v2/info/region_info.py +++ /dev/null @@ -1,39 +0,0 @@ -import functools -import logging -from spaceone.api.inventory.v2 import region_pb2 -from spaceone.core.pygrpc.message_type import * -from spaceone.core import utils -from spaceone.inventory_v2.model.region.region_model import Region - -__all__ = ["RegionInfo", "RegionsInfo"] - -_LOGGER = logging.getLogger(__name__) - - -def RegionInfo(region_vo: Region, minimal=False): - info = { - "region_id": region_vo.region_id, - "name": region_vo.name, - "region_code": region_vo.region_code, - "provider": region_vo.provider, - } - - if not minimal: - info.update( - { - "region_key": region_vo.region_key, - "tags": change_struct_type(region_vo.tags), - "domain_id": region_vo.domain_id, - "created_at": utils.datetime_to_iso8601(region_vo.created_at), - "updated_at": utils.datetime_to_iso8601(region_vo.updated_at), - } - ) - - return region_pb2.RegionInfo(**info) - - -def RegionsInfo(region_vos, total_count, **kwargs): - return region_pb2.RegionsInfo( - results=list(map(functools.partial(RegionInfo, **kwargs), region_vos)), - total_count=total_count, - ) diff --git a/src/spaceone/inventory_v2/interface/grpc/region.py b/src/spaceone/inventory_v2/interface/grpc/region.py index 88ab2af..239435a 100644 --- a/src/spaceone/inventory_v2/interface/grpc/region.py +++ b/src/spaceone/inventory_v2/interface/grpc/region.py @@ -1,45 +1,45 @@ -from spaceone.api.inventory.v2 import region_pb2, region_pb2_grpc +from spaceone.api.inventory_v2.v1 import region_pb2, region_pb2_grpc from spaceone.core.pygrpc import BaseAPI -class Region(BaseAPI, region_pb2_grpc.RegionServicer): +from spaceone.inventory_v2.service.region_service import RegionService + +class Region(BaseAPI, region_pb2_grpc.RegionServicer): pb2 = region_pb2 pb2_grpc = region_pb2_grpc def create(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RegionService', metadata) as region_service: - return self.locator.get_info('RegionInfo', region_service.create(params)) + region_svc = RegionService(metadata) + response: dict = region_svc.create(params) + return self.dict_to_message(response) def update(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RegionService', metadata) as region_service: - return self.locator.get_info('RegionInfo', region_service.update(params)) + region_svc = RegionService(metadata) + response: dict = region_svc.update(params) + return self.dict_to_message(response) def delete(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RegionService', metadata) as region_service: - region_service.delete(params) - return self.locator.get_info('EmptyInfo') + region_svc = RegionService(metadata) + region_svc.delete(params) + return self.empty() def get(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RegionService', metadata) as region_service: - return self.locator.get_info('RegionInfo', region_service.get(params)) + region_svc = RegionService(metadata) + response: dict = region_svc.get(params) + return self.dict_to_message(response) def list(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RegionService', metadata) as region_service: - region_vos, total_count = region_service.list(params) - return self.locator.get_info('RegionsInfo', region_vos, total_count, minimal=self.get_minimal(params)) + region_svc = RegionService(metadata) + response: dict = region_svc.list(params) + return self.dict_to_message(response) def stat(self, request, context): params, metadata = self.parse_request(request, context) - - with self.locator.get_service('RegionService', metadata) as region_service: - return self.locator.get_info('StatisticsInfo', region_service.stat(params)) \ No newline at end of file + region_svc = RegionService(metadata) + response: dict = region_svc.stat(params) + return self.dict_to_message(response) diff --git a/src/spaceone/inventory_v2/manager/region_manager.py b/src/spaceone/inventory_v2/manager/region_manager.py index 9f489df..82de5a9 100644 --- a/src/spaceone/inventory_v2/manager/region_manager.py +++ b/src/spaceone/inventory_v2/manager/region_manager.py @@ -1,21 +1,20 @@ import logging -from typing import Tuple +from typing import Tuple, Union -from spaceone.core.model.mongo_model import QuerySet from spaceone.core.manager import BaseManager +from spaceone.core.model.mongo_model import QuerySet +from spaceone.inventory_v2.model.region.database import Region from spaceone.inventory_v2.lib.resource_manager import ResourceManager -from spaceone.inventory_v2.model.region.region_model import Region + +__ALL__ = ["RegionManager"] _LOGGER = logging.getLogger(__name__) class RegionManager(BaseManager, ResourceManager): - resource_keys = ["region_id"] - query_method = "list_regions" - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.region_model: Region = self.locator.get_model("Region") + self.region_model = Region() def create_region(self, params: dict) -> Region: def _rollback(vo: Region): @@ -24,7 +23,6 @@ def _rollback(vo: Region): region_vo: Region = self.region_model.create(params) self.transaction.add_rollback(_rollback, region_vo) - return region_vo def update_region_by_vo(self, params: dict, region_vo: Region) -> Region: @@ -41,11 +39,16 @@ def _rollback(old_data): def delete_region_by_vo(region_vo: Region) -> None: region_vo.delete() - def get_region(self, region_id: str, domain_id: str) -> Region: + def get_region( + self, + region_id: str, + domain_id: str, + workspace_id: Union[list, str, None] = None, + ) -> Region: conditions = {"region_id": region_id, "domain_id": domain_id} - # if workspace_id: - # conditions.update({"workspace_id": workspace_id}) + if workspace_id: + conditions.update({"workspace_id": workspace_id}) return self.region_model.get(**conditions) diff --git a/src/spaceone/inventory_v2/model/__init__.py b/src/spaceone/inventory_v2/model/__init__.py index 6755fe4..6dbdd9f 100644 --- a/src/spaceone/inventory_v2/model/__init__.py +++ b/src/spaceone/inventory_v2/model/__init__.py @@ -1,6 +1,6 @@ from spaceone.inventory_v2.model.asset.database import Asset from spaceone.inventory_v2.model.asset_type.database import AssetType -from spaceone.inventory_v2.model.region.region_model import Region +from spaceone.inventory_v2.model.region.database import Region from spaceone.inventory_v2.model.collector.database import Collector from spaceone.inventory_v2.model.collector_rule.database import CollectorRule from spaceone.inventory_v2.model.collection_state.database import CollectionState diff --git a/src/spaceone/inventory_v2/model/asset/database.py b/src/spaceone/inventory_v2/model/asset/database.py index 8accf3b..597cdb3 100644 --- a/src/spaceone/inventory_v2/model/asset/database.py +++ b/src/spaceone/inventory_v2/model/asset/database.py @@ -5,7 +5,7 @@ from spaceone.inventory_v2.model.asset_type.database import AssetType from spaceone.inventory_v2.error.asset import ERROR_RESOURCE_ALREADY_DELETED -from spaceone.inventory_v2.model.region.region_model import Region +from spaceone.inventory_v2.model.region.database import Region class Asset(MongoModel): diff --git a/src/spaceone/inventory_v2/model/collector/request.py b/src/spaceone/inventory_v2/model/collector/request.py index 1cbd1e1..f7e34f5 100644 --- a/src/spaceone/inventory_v2/model/collector/request.py +++ b/src/spaceone/inventory_v2/model/collector/request.py @@ -71,7 +71,7 @@ class CollectorGetRequest(BaseModel): class CollectorSearchQueryRequest(BaseModel): query: Union[dict, None] = None collector_id: Union[str, None] = None - workspace_id: Union[str, list, None] = None + workspace_id: Union[list, str, None] = None domain_id: str diff --git a/src/spaceone/inventory_v2/model/region/database.py b/src/spaceone/inventory_v2/model/region/database.py new file mode 100644 index 0000000..02ced02 --- /dev/null +++ b/src/spaceone/inventory_v2/model/region/database.py @@ -0,0 +1,28 @@ +from mongoengine import * + +from spaceone.core.model.mongo_model import MongoModel + + +class Region(MongoModel): + region_id = StringField(max_length=40, unique=True) + name = StringField(max_length=255) + region_code = StringField(max_length=255) + provider = StringField(max_length=255) + tags = DictField() + resource_group = StringField(max_length=40, choices=("DOMAIN", "WORKSPACE")) + workspace_id = StringField(max_length=40) + domain_id = StringField(max_length=40) + created_at = DateTimeField(auto_now_add=True) + updated_at = DateTimeField(auto_now=True) + + meta = { + "updatable_fields": ["name", "tags", "updated_at"], + "minimal_fields": ["region_id", "name", "region_code", "provider"], + "ordering": ["name"], + "indexes": [ + "provider", + "resource_group", + "workspace_id", + "domain_id", + ], + } diff --git a/src/spaceone/inventory_v2/model/region/region_model.py b/src/spaceone/inventory_v2/model/region/region_model.py deleted file mode 100644 index 2b51044..0000000 --- a/src/spaceone/inventory_v2/model/region/region_model.py +++ /dev/null @@ -1,49 +0,0 @@ -from mongoengine import * -from spaceone.core.model.mongo_model import MongoModel - - -class Region(MongoModel): - region_id = StringField(max_length=40, generate_id="region", unique=True) - name = StringField(max_length=255) - region_key = StringField(max_length=255) - region_code = StringField( - max_length=255, unique_with=["provider", "domain_id"] - ) - provider = StringField(max_length=255) - ref_region = StringField(max_length=255) - tags = DictField() - domain_id = StringField(max_length=40) - updated_by = StringField(default=None, null=True) - created_at = DateTimeField(auto_now_add=True) - updated_at = DateTimeField(auto_now=True) - - meta = { - "updatable_fields": ["name", "tags", "updated_by", "updated_at"], - "minimal_fields": ["region_id", "name", "region_code", "provider"], - "ordering": ["name"], - "indexes": [ - { - "fields": ["domain_id", "-updated_at", "updated_by"], - "name": "COMPOUND_INDEX_FOR_GC_1", - }, - { - "fields": ["domain_id", "region_id"], - "name": "COMPOUND_INDEX_FOR_SEARCH_1", - }, - { - "fields": ["domain_id", "provider", "region_code"], - "name": "COMPOUND_INDEX_FOR_SEARCH_2", - }, - { - "fields": ["domain_id", "region_key"], - "name": "COMPOUND_INDEX_FOR_SEARCH_3", - }, - {"fields": ["region_id", "ref_region"], "name": "COMPOUND_INDEX_FOR_REF_1"}, - { - "fields": ["region_code", "provider", "ref_region"], - "name": "COMPOUND_INDEX_FOR_REF_2", - }, - "ref_region", - "domain_id", - ], - } diff --git a/src/spaceone/inventory_v2/model/region/request.py b/src/spaceone/inventory_v2/model/region/request.py new file mode 100644 index 0000000..a61e747 --- /dev/null +++ b/src/spaceone/inventory_v2/model/region/request.py @@ -0,0 +1,60 @@ +from typing import Union, Literal +from pydantic import BaseModel + +__all__ = [ + "RegionCreateRequest", + "RegionUpdateRequest", + "RegionDeleteRequest", + "RegionGetRequest", + "RegionSearchQueryRequest", + "RegionStatQueryRequest", +] + +ResourceGroup = Literal["DOMAIN", "WORKSPACE"] + + +class RegionCreateRequest(BaseModel): + name: str + region_code: str + provider: str + tags: Union[dict, None] = None + resource_group: ResourceGroup + workspace_id: Union[str, None] = None + domain_id: str + + +class RegionUpdateRequest(BaseModel): + region_id: str + name: Union[str, None] = None + tags: Union[dict, None] = None + workspace_id: Union[str, None] = None + domain_id: str + + +class RegionDeleteRequest(BaseModel): + region_id: str + workspace_id: Union[str, None] = None + domain_id: str + + +class RegionGetRequest(BaseModel): + region_id: str + workspace_id: Union[list, str, None] = None + domain_id: str + + +class RegionSearchQueryRequest(BaseModel): + query: Union[dict, None] = None + region_id: Union[str, None] = None + name: Union[str, None] = None + region_code: Union[str, None] = None + provider: Union[str, None] = None + exists_only: Union[bool, None] = None + workspace_id: Union[list, str, None] = None + domain_id: str + + +class RegionStatQueryRequest(BaseModel): + query: dict + workspace_id: Union[list, str, None] = None + domain_id: str diff --git a/src/spaceone/inventory_v2/model/region/response.py b/src/spaceone/inventory_v2/model/region/response.py new file mode 100644 index 0000000..bbaa319 --- /dev/null +++ b/src/spaceone/inventory_v2/model/region/response.py @@ -0,0 +1,29 @@ +from datetime import datetime +from typing import Union, List +from pydantic import BaseModel + +from spaceone.core import utils + +__all__ = ["RegionResponse", "RegionsResponse"] + + +class RegionResponse(BaseModel): + region_id: Union[str, None] = None + name: Union[str, None] = None + region_code: Union[str, None] = None + provider: Union[str, None] = None + tags: Union[dict, None] = None + domain_id: Union[str, None] = None + created_at: Union[datetime, None] = None + updated_at: Union[datetime, None] = None + + def dict(self, *args, **kwargs): + data = super().dict(*args, **kwargs) + data["created_at"] = utils.datetime_to_iso8601(data["created_at"]) + data["updated_at"] = utils.datetime_to_iso8601(data["updated_at"]) + return data + + +class RegionsResponse(BaseModel): + results: List[RegionResponse] + total_count: int diff --git a/src/spaceone/inventory_v2/service/collector_service.py b/src/spaceone/inventory_v2/service/collector_service.py index b05f548..3520ed0 100644 --- a/src/spaceone/inventory_v2/service/collector_service.py +++ b/src/spaceone/inventory_v2/service/collector_service.py @@ -317,7 +317,6 @@ def verify_plugin(self, params: CollectorVerifyPluginRequest) -> None: permission="inventory-v2:Collector.write", role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], ) - @check_required(["collector_id", "domain_id"]) @convert_model def delete(self, params: CollectorDeleteRequest) -> None: """Delete collector @@ -389,7 +388,6 @@ def get(self, params: CollectorGetRequest) -> Union[CollectorResponse, dict]: permission="inventory-v2:Collector.read", role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], ) - @check_required(["domain_id"]) @append_query_filter( [ "collector_id", diff --git a/src/spaceone/inventory_v2/service/region_service.py b/src/spaceone/inventory_v2/service/region_service.py index 6c0e256..c0766f2 100644 --- a/src/spaceone/inventory_v2/service/region_service.py +++ b/src/spaceone/inventory_v2/service/region_service.py @@ -1,10 +1,14 @@ import logging -from typing import Tuple +from typing import Union + +from spaceone.core.error import * from spaceone.core.service import * -from spaceone.core import utils -from spaceone.core.model.mongo_model import QuerySet + from spaceone.inventory_v2.manager.region_manager import RegionManager -from spaceone.inventory_v2.model.region.region_model import Region +from spaceone.inventory_v2.manager.identity_manager import IdentityManager +from spaceone.inventory_v2.model.region.request import * +from spaceone.inventory_v2.model.region.response import * +from spaceone.inventory_v2.model.region.database import Region _LOGGER = logging.getLogger(__name__) _KEYWORD_FILTER = ["region_id", "name", "region_code"] @@ -19,104 +23,122 @@ class RegionService(BaseService): def __init__(self, metadata): super().__init__(metadata) - self.region_mgr: RegionManager = self.locator.get_manager("RegionManager") + self.region_mgr = RegionManager() @transaction( - permission="inventory:Region.write", - role_types=["DOMAIN_ADMIN"], + permission="inventory-v2:Region.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], ) - def create(self, params: dict) -> Region: + @convert_model + def create(self, params: RegionCreateRequest) -> Union[RegionResponse, dict]: """ Args: params (dict): { - 'name': 'str', # required - 'region_code': 'str', # required - 'provider': 'str', # required + 'name': 'str', # required + 'region_code': 'str', # required + 'provider': 'str', # required 'tags': 'dict', - 'domain_id': 'str', # injected from auth (required) + 'resource_group': 'str', # required + 'workspace_id': 'str', # injected from auth + 'domain_id': 'str', # injected from auth (required) } Returns: - region_vo (object) + RegionResponse: """ + region_vo = self.create_resource(params.dict()) - return self.create_resource(params) + return RegionResponse(**region_vo.to_dict()) - @check_required(["name", "region_code", "provider", "domain_id"]) def create_resource(self, params: dict) -> Region: - if "tags" in params: - if isinstance(params["tags"], list): - params["tags"] = utils.tags_to_dict(params["tags"]) domain_id = params["domain_id"] + workspace_id = params.get("workspace_id") + resource_group = params["resource_group"] + + # Check permission by resource group + if resource_group == "WORKSPACE": + if workspace_id is None: + raise ERROR_REQUIRED_PARAMETER(key="workspace_id") + + identity_mgr = IdentityManager() + identity_mgr.check_workspace(workspace_id, domain_id) + else: + params["workspace_id"] = "*" - params["updated_by"] = self.transaction.get_meta("collector_id") or "manual" - params["region_key"] = f'{params["provider"]}.{params["region_code"]}' - # params["ref_region"] = f'{domain_id}.{workspace_id}.{params["region_key"]}' + region_id = f'{params["provider"]}-{params["region_code"]}' + + params["region_id"] = region_id return self.region_mgr.create_region(params) @transaction( - permission="inventory:Region.write", - role_types=["DOMAIN_ADMIN"], + permission="inventory-v2:Region.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], ) - def update(self, params: dict) -> Region: + @convert_model + def update(self, params: RegionUpdateRequest) -> Union[RegionResponse, dict]: """ Args: params (dict): { 'region_id': 'str', # required 'name': 'str', 'tags': 'dict', + 'workspace_id': 'str', # injected from auth 'domain_id': 'str', # injected from auth (required) } Returns: region_vo (object) """ - return self.update_resource(params) + region_vo = self.update_resource(params.dict(exclude_unset=True)) + + return RegionResponse(**region_vo.to_dict()) - @check_required(["region_id", "domain_id"]) def update_resource(self, params: dict) -> Region: - if "tags" in params: - if isinstance(params["tags"], list): - params["tags"] = utils.tags_to_dict(params["tags"]) - params["updated_by"] = self.transaction.get_meta("collector_id") or "manual" + region_vo = self.region_mgr.get_region( + params["region_id"], params["domain_id"], params.get("workspace_id") + ) - region_vo = self.region_mgr.get_region(params["region_id"], params["domain_id"]) return self.region_mgr.update_region_by_vo(params, region_vo) @transaction( - permission="inventory:Region.write", - role_types=["DOMAIN_ADMIN"], + permission="inventory-v2:Region.write", + role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER"], ) - def delete(self, params: dict) -> None: + @convert_model + def delete(self, params: RegionDeleteRequest) -> None: """ Args: params (dict): { 'region_id': 'str', # required + 'workspace_id': 'str', # injected from auth 'domain_id': 'str' # injected from auth (required) } Returns: None """ - self.delete_resource(params) + self.delete_resource(params.dict()) - @check_required(["region_id", "domain_id"]) def delete_resource(self, params: dict) -> None: - region_vo = self.region_mgr.get_region(params["region_id"], params["domain_id"]) + region_vo = self.region_mgr.get_region( + params["region_id"], params["domain_id"], params.get("workspace_id") + ) self.region_mgr.delete_region_by_vo(region_vo) @transaction( - permission="inventory:Region.read", + permission="inventory-v2:Region.read", role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], ) - @check_required(["region_id", "domain_id"]) - def get(self, params: dict) -> Region: + @change_value_by_rule("APPEND", "workspace_id", "*") + @convert_model + def get(self, params: RegionGetRequest) -> Union[RegionResponse, dict]: """ Args: params (dict): { 'region_id': 'str', # required + 'workspace_id': 'str', # injected from auth 'domain_id': 'str', # injected from auth (required) } @@ -125,34 +147,41 @@ def get(self, params: dict) -> Region: """ - return self.region_mgr.get_region(params["region_id"], params["domain_id"]) + region_vo = self.region_mgr.get_region( + params.region_id, params.domain_id, params.workspace_id + ) + + return RegionResponse(**region_vo.to_dict()) @transaction( - permission="inventory:Region.read", + permission="inventory-v2:Region.read", role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], ) - @check_required(["domain_id"]) @append_query_filter( [ "region_id", "name", - "region_key", "region_code", "provider", + "exists_only", + "workspace_id", "domain_id", ] ) @append_keyword_filter(_KEYWORD_FILTER) - def list(self, params: dict) -> Tuple[QuerySet, int]: + @change_value_by_rule("APPEND", "workspace_id", "*") + @convert_model + def list(self, params: RegionSearchQueryRequest) -> Union[RegionsResponse, dict]: """ Args: params (dict): { 'query': 'dict (spaceone.api.core.v1.Query)', 'region_id': 'str', 'name': 'str', - 'region_key': 'str', 'region_code': 'str', 'provider': 'str', + 'exists_only': 'bool', + 'workspace_id': 'str', # injected from auth 'domain_id': 'str', # injected from auth (required) } @@ -162,16 +191,22 @@ def list(self, params: dict) -> Tuple[QuerySet, int]: """ - return self.region_mgr.list_regions(params.get("query", {})) + query = params.query or {} + region_vos, total_count = self.region_mgr.list_regions(query=query) + + regions_info = [region_vo.to_dict() for region_vo in region_vos] + + return RegionsResponse(results=regions_info, total_count=total_count) @transaction( - permission="inventory:Region.read", + permission="inventory-v2:Region.read", role_types=["DOMAIN_ADMIN", "WORKSPACE_OWNER", "WORKSPACE_MEMBER"], ) - @check_required(["query", "domain_id"]) - @append_query_filter(["domain_id"]) + @append_query_filter(["workspace_id", "domain_id"]) @append_keyword_filter(_KEYWORD_FILTER) - def stat(self, params: dict) -> dict: + @change_value_by_rule("APPEND", "workspace_id", "*") + @convert_model + def stat(self, params: RegionStatQueryRequest) -> dict: """ Args: params (dict): { @@ -184,5 +219,5 @@ def stat(self, params: dict) -> dict: """ - query = params.get("query", {}) + query = params.query or {} return self.region_mgr.stat_regions(query)