diff --git a/examples/test_global_tagging_v1_examples.py b/examples/test_global_tagging_v1_examples.py index 8633764..52499ab 100644 --- a/examples/test_global_tagging_v1_examples.py +++ b/examples/test_global_tagging_v1_examples.py @@ -128,10 +128,8 @@ def test_attach_tag_example(self): print('\nattach_tag() result:') # begin-attach_tag - resource_model = {'resource_id': resource_crn} - tag_results = global_tagging_service.attach_tag( - resources=[resource_model], tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' + tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' ).get_result() print(json.dumps(tag_results, indent=2)) @@ -150,10 +148,8 @@ def test_detach_tag_example(self): print('\ndetach_tag() result:') # begin-detach_tag - resource_model = {'resource_id': resource_crn} - tag_results = global_tagging_service.detach_tag( - resources=[resource_model], tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' + tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' ).get_result() print(json.dumps(tag_results, indent=2)) diff --git a/ibm_platform_services/global_tagging_v1.py b/ibm_platform_services/global_tagging_v1.py index aaf367e..dd18ed6 100644 --- a/ibm_platform_services/global_tagging_v1.py +++ b/ibm_platform_services/global_tagging_v1.py @@ -1,6 +1,6 @@ # coding: utf-8 -# (C) Copyright IBM Corp. 2024. +# (C) Copyright IBM Corp. 2025. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# IBM OpenAPI SDK Code Generator Version: 3.87.0-91c7c775-20240320-213027 +# IBM OpenAPI SDK Code Generator Version: 3.105.0-3c13b041-20250605-193116 """ Manage your tags with the Tagging API in IBM Cloud. You can attach, detach, delete, or @@ -23,10 +23,10 @@ `label`. The tagging API supports three types of tag: `user` `service`, and `access` tags. `service` tags cannot be attached to IMS resources. `service` tags must be in the form `service_prefix:tag_label` where `service_prefix` identifies the Service owning the tag. -`access` tags cannot be attached to IMS and Cloud Foundry resources. They must be in the -form `key:value`. You can replace all resource's tags using the `replace` query parameter -in the attach operation. You can update the `value` of a resource's tag in the format -`key:value`, using the `update` query parameter in the attach operation. +`access` tags cannot be attached to IMS resources. They must be in the form `key:value`. +You can replace all resource's tags using the `replace` query parameter in the attach +operation. You can update the `value` of a resource's tag in the format `key:value`, using +the `update` query parameter in the attach operation. API Version: 1.2.0 """ @@ -453,10 +453,11 @@ def delete_tag( def attach_tag( self, - resources: List['Resource'], *, tag_name: Optional[str] = None, tag_names: Optional[List[str]] = None, + resources: Optional[List['Resource']] = None, + query: Optional['QueryString'] = None, x_request_id: Optional[str] = None, x_correlation_id: Optional[str] = None, account_id: Optional[str] = None, @@ -472,10 +473,11 @@ def attach_tag( than 1000 tags per each 'user' and 'service' type, and no more than 250 'access' tags (which is the account limit). - :param List[Resource] resources: List of resources on which the tag or tags - are attached. :param str tag_name: (optional) The name of the tag to attach. :param List[str] tag_names: (optional) An array of tag names to attach. + :param List[Resource] resources: (optional) List of resources on which the + tagging operation operates on. + :param QueryString query: (optional) A valid Global Search string. :param str x_request_id: (optional) An alphanumeric string that is used to trace the request. The value may include ASCII alphanumerics and any of following segment separators: space ( ), comma (,), hyphen, (-), and @@ -516,9 +518,10 @@ def attach_tag( :rtype: DetailedResponse with `dict` result representing a `TagResults` object """ - if resources is None: - raise ValueError('resources must be provided') - resources = [convert_model(x) for x in resources] + if resources is not None: + resources = [convert_model(x) for x in resources] + if query is not None: + query = convert_model(query) headers = { 'x-request-id': x_request_id, 'x-correlation-id': x_correlation_id, @@ -538,9 +541,10 @@ def attach_tag( } data = { - 'resources': resources, 'tag_name': tag_name, 'tag_names': tag_names, + 'resources': resources, + 'query': query, } data = {k: v for (k, v) in data.items() if v is not None} data = json.dumps(data) @@ -565,10 +569,11 @@ def attach_tag( def detach_tag( self, - resources: List['Resource'], *, tag_name: Optional[str] = None, tag_names: Optional[List[str]] = None, + resources: Optional[List['Resource']] = None, + query: Optional['QueryString'] = None, x_request_id: Optional[str] = None, x_correlation_id: Optional[str] = None, account_id: Optional[str] = None, @@ -580,10 +585,11 @@ def detach_tag( Detaches one or more tags from one or more resources. - :param List[Resource] resources: List of resources on which the tag or tags - are detached. :param str tag_name: (optional) The name of the tag to detach. :param List[str] tag_names: (optional) An array of tag names to detach. + :param List[Resource] resources: (optional) List of resources on which the + tagging operation operates on. + :param QueryString query: (optional) A valid Global Search string. :param str x_request_id: (optional) An alphanumeric string that is used to trace the request. The value may include ASCII alphanumerics and any of following segment separators: space ( ), comma (,), hyphen, (-), and @@ -612,9 +618,10 @@ def detach_tag( :rtype: DetailedResponse with `dict` result representing a `TagResults` object """ - if resources is None: - raise ValueError('resources must be provided') - resources = [convert_model(x) for x in resources] + if resources is not None: + resources = [convert_model(x) for x in resources] + if query is not None: + query = convert_model(query) headers = { 'x-request-id': x_request_id, 'x-correlation-id': x_correlation_id, @@ -632,9 +639,10 @@ def detach_tag( } data = { - 'resources': resources, 'tag_name': tag_name, 'tag_names': tag_names, + 'resources': resources, + 'query': query, } data = {k: v for (k, v) in data.items() if v is not None} data = json.dumps(data) @@ -1001,6 +1009,8 @@ class DeleteTagResultsItem: :param str provider: (optional) The provider of the tag. :param bool is_error: (optional) It is `true` if the operation exits with an error (for example, the tag does not exist). + + This type supports additional properties of type object. """ # The set of defined properties for the class @@ -1011,7 +1021,7 @@ def __init__( *, provider: Optional[str] = None, is_error: Optional[bool] = None, - **kwargs, + **kwargs: Optional[object], ) -> None: """ Initialize a DeleteTagResultsItem object. @@ -1019,12 +1029,17 @@ def __init__( :param str provider: (optional) The provider of the tag. :param bool is_error: (optional) It is `true` if the operation exits with an error (for example, the tag does not exist). - :param **kwargs: (optional) Any additional properties. + :param object **kwargs: (optional) Additional properties of type object """ self.provider = provider self.is_error = is_error - for _key, _value in kwargs.items(): - setattr(self, _key, _value) + for k, v in kwargs.items(): + if k not in DeleteTagResultsItem._properties: + if not isinstance(v, object): + raise ValueError('Value for additional property {} must be of type object'.format(k)) + setattr(self, k, v) + else: + raise ValueError('Property {} cannot be specified as an additional property'.format(k)) @classmethod def from_dict(cls, _dict: Dict) -> 'DeleteTagResultsItem': @@ -1034,7 +1049,11 @@ def from_dict(cls, _dict: Dict) -> 'DeleteTagResultsItem': args['provider'] = provider if (is_error := _dict.get('is_error')) is not None: args['is_error'] = is_error - args.update({k: v for (k, v) in _dict.items() if k not in cls._properties}) + for k, v in _dict.items(): + if k not in cls._properties: + if not isinstance(v, object): + raise ValueError('Value for additional property {} must be of type object'.format(k)) + args[k] = v return cls(**args) @classmethod @@ -1049,8 +1068,8 @@ def to_dict(self) -> Dict: _dict['provider'] = self.provider if hasattr(self, 'is_error') and self.is_error is not None: _dict['is_error'] = self.is_error - for _key in [k for k in vars(self).keys() if k not in DeleteTagResultsItem._properties]: - _dict[_key] = getattr(self, _key) + for k in [_k for _k in vars(self).keys() if _k not in DeleteTagResultsItem._properties]: + _dict[k] = getattr(self, k) return _dict def _to_dict(self): @@ -1058,21 +1077,23 @@ def _to_dict(self): return self.to_dict() def get_properties(self) -> Dict: - """Return a dictionary of arbitrary properties from this instance of DeleteTagResultsItem""" + """Return the additional properties from this instance of DeleteTagResultsItem in the form of a dict.""" _dict = {} - - for _key in [k for k in vars(self).keys() if k not in DeleteTagResultsItem._properties]: - _dict[_key] = getattr(self, _key) + for k in [_k for _k in vars(self).keys() if _k not in DeleteTagResultsItem._properties]: + _dict[k] = getattr(self, k) return _dict def set_properties(self, _dict: dict): - """Set a dictionary of arbitrary properties to this instance of DeleteTagResultsItem""" - for _key in [k for k in vars(self).keys() if k not in DeleteTagResultsItem._properties]: - delattr(self, _key) - - for _key, _value in _dict.items(): - if _key not in DeleteTagResultsItem._properties: - setattr(self, _key, _value) + """Set a dictionary of additional properties in this instance of DeleteTagResultsItem""" + for k in [_k for _k in vars(self).keys() if _k not in DeleteTagResultsItem._properties]: + delattr(self, k) + for k, v in _dict.items(): + if k not in DeleteTagResultsItem._properties: + if not isinstance(v, object): + raise ValueError('Value for additional property {} must be of type object'.format(k)) + setattr(self, k, v) + else: + raise ValueError('Property {} cannot be specified as an additional property'.format(k)) def __str__(self) -> str: """Return a `str` version of this DeleteTagResultsItem object.""" @@ -1248,12 +1269,76 @@ def __ne__(self, other: 'DeleteTagsResultItem') -> bool: return not self == other +class QueryString: + """ + A valid Global Search string. + + :param str query_string: The Lucene-formatted query string. + """ + + def __init__( + self, + query_string: str, + ) -> None: + """ + Initialize a QueryString object. + + :param str query_string: The Lucene-formatted query string. + """ + self.query_string = query_string + + @classmethod + def from_dict(cls, _dict: Dict) -> 'QueryString': + """Initialize a QueryString object from a json dictionary.""" + args = {} + if (query_string := _dict.get('query_string')) is not None: + args['query_string'] = query_string + else: + raise ValueError('Required property \'query_string\' not present in QueryString JSON') + return cls(**args) + + @classmethod + def _from_dict(cls, _dict): + """Initialize a QueryString object from a json dictionary.""" + return cls.from_dict(_dict) + + def to_dict(self) -> Dict: + """Return a json dictionary representing this model.""" + _dict = {} + if hasattr(self, 'query_string') and self.query_string is not None: + _dict['query_string'] = self.query_string + return _dict + + def _to_dict(self): + """Return a json dictionary representing this model.""" + return self.to_dict() + + def __str__(self) -> str: + """Return a `str` version of this QueryString object.""" + return json.dumps(self.to_dict(), indent=2) + + def __eq__(self, other: 'QueryString') -> bool: + """Return `true` when self and other are equal, false otherwise.""" + if not isinstance(other, self.__class__): + return False + return self.__dict__ == other.__dict__ + + def __ne__(self, other: 'QueryString') -> bool: + """Return `true` when self and other are not equal, false otherwise.""" + return not self == other + + class Resource: """ A resource that might have tags that are attached. :param str resource_id: The CRN or IMS ID of the resource. - :param str resource_type: (optional) The IMS resource type of the resource. + :param str resource_type: (optional) The IMS resource type of the resource. It + can be one of SoftLayer_Virtual_DedicatedHost, SoftLayer_Hardware, + SoftLayer_Hardware_Server, SoftLayer_Network_Application_Delivery_Controller, + SoftLayer_Network_Vlan, SoftLayer_Network_Vlan_Firewall, + SoftLayer_Network_Component_Firewall, SoftLayer_Network_Firewall_Module_Context, + SoftLayer_Virtual_Guest. """ def __init__( @@ -1267,6 +1352,11 @@ def __init__( :param str resource_id: The CRN or IMS ID of the resource. :param str resource_type: (optional) The IMS resource type of the resource. + It can be one of SoftLayer_Virtual_DedicatedHost, SoftLayer_Hardware, + SoftLayer_Hardware_Server, + SoftLayer_Network_Application_Delivery_Controller, SoftLayer_Network_Vlan, + SoftLayer_Network_Vlan_Firewall, SoftLayer_Network_Component_Firewall, + SoftLayer_Network_Firewall_Module_Context, SoftLayer_Virtual_Guest. """ self.resource_id = resource_id self.resource_type = resource_type diff --git a/test/unit/test_global_tagging_v1.py b/test/unit/test_global_tagging_v1.py index 2e6af5e..2f34f5d 100644 --- a/test/unit/test_global_tagging_v1.py +++ b/test/unit/test_global_tagging_v1.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# (C) Copyright IBM Corp. 2024. +# (C) Copyright IBM Corp. 2025. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -42,15 +42,8 @@ def preprocess_url(operation_path: str): The returned request URL is used to register the mock response so it needs to match the request URL that is formed by the requests library. """ - # First, unquote the path since it might have some quoted/escaped characters in it - # due to how the generator inserts the operation paths into the unit test code. - operation_path = urllib.parse.unquote(operation_path) - # Next, quote the path using urllib so that we approximate what will - # happen during request processing. - operation_path = urllib.parse.quote(operation_path, safe='/') - - # Finally, form the request URL from the base URL and operation path. + # Form the request URL from the base URL and operation path. request_url = _base_url + operation_path # If the request url does NOT end with a /, then just return it as-is. @@ -594,10 +587,15 @@ def test_attach_tag_all_params(self): resource_model['resource_id'] = 'testString' resource_model['resource_type'] = 'testString' + # Construct a dict representation of a QueryString model + query_string_model = {} + query_string_model['query_string'] = 'testString' + # Set up parameter values - resources = [resource_model] tag_name = 'testString' tag_names = ['testString'] + resources = [resource_model] + query = query_string_model x_request_id = 'testString' x_correlation_id = 'testString' account_id = 'testString' @@ -607,9 +605,10 @@ def test_attach_tag_all_params(self): # Invoke method response = _service.attach_tag( - resources, tag_name=tag_name, tag_names=tag_names, + resources=resources, + query=query, x_request_id=x_request_id, x_correlation_id=x_correlation_id, account_id=account_id, @@ -631,9 +630,10 @@ def test_attach_tag_all_params(self): assert 'update={}'.format('true' if update else 'false') in query_string # Validate body params req_body = json.loads(str(responses.calls[0].request.body, 'utf-8')) - assert req_body['resources'] == [resource_model] assert req_body['tag_name'] == 'testString' assert req_body['tag_names'] == ['testString'] + assert req_body['resources'] == [resource_model] + assert req_body['query'] == query_string_model def test_attach_tag_all_params_with_retries(self): # Enable retries and run test_attach_tag_all_params. @@ -665,16 +665,22 @@ def test_attach_tag_required_params(self): resource_model['resource_id'] = 'testString' resource_model['resource_type'] = 'testString' + # Construct a dict representation of a QueryString model + query_string_model = {} + query_string_model['query_string'] = 'testString' + # Set up parameter values - resources = [resource_model] tag_name = 'testString' tag_names = ['testString'] + resources = [resource_model] + query = query_string_model # Invoke method response = _service.attach_tag( - resources, tag_name=tag_name, tag_names=tag_names, + resources=resources, + query=query, headers={}, ) @@ -683,9 +689,10 @@ def test_attach_tag_required_params(self): assert response.status_code == 200 # Validate body params req_body = json.loads(str(responses.calls[0].request.body, 'utf-8')) - assert req_body['resources'] == [resource_model] assert req_body['tag_name'] == 'testString' assert req_body['tag_names'] == ['testString'] + assert req_body['resources'] == [resource_model] + assert req_body['query'] == query_string_model def test_attach_tag_required_params_with_retries(self): # Enable retries and run test_attach_tag_required_params. @@ -696,50 +703,6 @@ def test_attach_tag_required_params_with_retries(self): _service.disable_retries() self.test_attach_tag_required_params() - @responses.activate - def test_attach_tag_value_error(self): - """ - test_attach_tag_value_error() - """ - # Set up mock - url = preprocess_url('/v3/tags/attach') - mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true}]}' - responses.add( - responses.POST, - url, - body=mock_response, - content_type='application/json', - status=200, - ) - - # Construct a dict representation of a Resource model - resource_model = {} - resource_model['resource_id'] = 'testString' - resource_model['resource_type'] = 'testString' - - # Set up parameter values - resources = [resource_model] - tag_name = 'testString' - tag_names = ['testString'] - - # Pass in all but one required param and check for a ValueError - req_param_dict = { - "resources": resources, - } - for param in req_param_dict.keys(): - req_copy = {key: val if key is not param else None for (key, val) in req_param_dict.items()} - with pytest.raises(ValueError): - _service.attach_tag(**req_copy) - - def test_attach_tag_value_error_with_retries(self): - # Enable retries and run test_attach_tag_value_error. - _service.enable_retries() - self.test_attach_tag_value_error() - - # Disable retries and run test_attach_tag_value_error. - _service.disable_retries() - self.test_attach_tag_value_error() - class TestDetachTag: """ @@ -767,10 +730,15 @@ def test_detach_tag_all_params(self): resource_model['resource_id'] = 'testString' resource_model['resource_type'] = 'testString' + # Construct a dict representation of a QueryString model + query_string_model = {} + query_string_model['query_string'] = 'testString' + # Set up parameter values - resources = [resource_model] tag_name = 'testString' tag_names = ['testString'] + resources = [resource_model] + query = query_string_model x_request_id = 'testString' x_correlation_id = 'testString' account_id = 'testString' @@ -778,9 +746,10 @@ def test_detach_tag_all_params(self): # Invoke method response = _service.detach_tag( - resources, tag_name=tag_name, tag_names=tag_names, + resources=resources, + query=query, x_request_id=x_request_id, x_correlation_id=x_correlation_id, account_id=account_id, @@ -798,9 +767,10 @@ def test_detach_tag_all_params(self): assert 'tag_type={}'.format(tag_type) in query_string # Validate body params req_body = json.loads(str(responses.calls[0].request.body, 'utf-8')) - assert req_body['resources'] == [resource_model] assert req_body['tag_name'] == 'testString' assert req_body['tag_names'] == ['testString'] + assert req_body['resources'] == [resource_model] + assert req_body['query'] == query_string_model def test_detach_tag_all_params_with_retries(self): # Enable retries and run test_detach_tag_all_params. @@ -832,16 +802,22 @@ def test_detach_tag_required_params(self): resource_model['resource_id'] = 'testString' resource_model['resource_type'] = 'testString' + # Construct a dict representation of a QueryString model + query_string_model = {} + query_string_model['query_string'] = 'testString' + # Set up parameter values - resources = [resource_model] tag_name = 'testString' tag_names = ['testString'] + resources = [resource_model] + query = query_string_model # Invoke method response = _service.detach_tag( - resources, tag_name=tag_name, tag_names=tag_names, + resources=resources, + query=query, headers={}, ) @@ -850,9 +826,10 @@ def test_detach_tag_required_params(self): assert response.status_code == 200 # Validate body params req_body = json.loads(str(responses.calls[0].request.body, 'utf-8')) - assert req_body['resources'] == [resource_model] assert req_body['tag_name'] == 'testString' assert req_body['tag_names'] == ['testString'] + assert req_body['resources'] == [resource_model] + assert req_body['query'] == query_string_model def test_detach_tag_required_params_with_retries(self): # Enable retries and run test_detach_tag_required_params. @@ -863,50 +840,6 @@ def test_detach_tag_required_params_with_retries(self): _service.disable_retries() self.test_detach_tag_required_params() - @responses.activate - def test_detach_tag_value_error(self): - """ - test_detach_tag_value_error() - """ - # Set up mock - url = preprocess_url('/v3/tags/detach') - mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true}]}' - responses.add( - responses.POST, - url, - body=mock_response, - content_type='application/json', - status=200, - ) - - # Construct a dict representation of a Resource model - resource_model = {} - resource_model['resource_id'] = 'testString' - resource_model['resource_type'] = 'testString' - - # Set up parameter values - resources = [resource_model] - tag_name = 'testString' - tag_names = ['testString'] - - # Pass in all but one required param and check for a ValueError - req_param_dict = { - "resources": resources, - } - for param in req_param_dict.keys(): - req_copy = {key: val if key is not param else None for (key, val) in req_param_dict.items()} - with pytest.raises(ValueError): - _service.detach_tag(**req_copy) - - def test_detach_tag_value_error_with_retries(self): - # Enable retries and run test_detach_tag_value_error. - _service.enable_retries() - self.test_detach_tag_value_error() - - # Disable retries and run test_detach_tag_value_error. - _service.disable_retries() - self.test_detach_tag_value_error() - # endregion ############################################################################## @@ -1069,7 +1002,7 @@ def test_delete_tag_results_item_serialization(self): expected_dict = {'foo': 'testString'} delete_tag_results_item_model.set_properties(expected_dict) actual_dict = delete_tag_results_item_model.get_properties() - assert actual_dict == expected_dict + assert actual_dict.keys() == expected_dict.keys() class TestModel_DeleteTagsResult: @@ -1141,6 +1074,36 @@ def test_delete_tags_result_item_serialization(self): assert delete_tags_result_item_model_json2 == delete_tags_result_item_model_json +class TestModel_QueryString: + """ + Test Class for QueryString + """ + + def test_query_string_serialization(self): + """ + Test serialization/deserialization for QueryString + """ + + # Construct a json representation of a QueryString model + query_string_model_json = {} + query_string_model_json['query_string'] = 'testString' + + # Construct a model instance of QueryString by calling from_dict on the json representation + query_string_model = QueryString.from_dict(query_string_model_json) + assert query_string_model != False + + # Construct a model instance of QueryString by calling from_dict on the json representation + query_string_model_dict = QueryString.from_dict(query_string_model_json).__dict__ + query_string_model2 = QueryString(**query_string_model_dict) + + # Verify the model instances are equivalent + assert query_string_model == query_string_model2 + + # Convert model instance back to dict and verify no loss of data + query_string_model_json2 = query_string_model.to_dict() + assert query_string_model_json2 == query_string_model_json + + class TestModel_Resource: """ Test Class for Resource