From 63abda54973e1f9d25c437ce2d3ad2b7dc2f485b Mon Sep 17 00:00:00 2001 From: Stefano Roberto Mollica Date: Thu, 12 Feb 2026 11:07:27 +0100 Subject: [PATCH 1/4] feat: add message field to TagResultsItem Add optional message field to TagResultsItem class to expose error messages returned by the GhoST Tags API when tagging operations fail. Changes: - Add message parameter to class docstring - Add message parameter to __init__ method - Add message field assignment in __init__ - Add message extraction in from_dict method Related to task #29830 Signed-off-by: Stefano Roberto Mollica --- ibm_platform_services/global_tagging_v1.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ibm_platform_services/global_tagging_v1.py b/ibm_platform_services/global_tagging_v1.py index dd18ed6..7b25934 100644 --- a/ibm_platform_services/global_tagging_v1.py +++ b/ibm_platform_services/global_tagging_v1.py @@ -1628,6 +1628,7 @@ class TagResultsItem: :param str resource_id: The CRN or IMS ID of the resource. :param bool is_error: (optional) It is `true` if the operation exits with an error. + :param str message: (optional) Error message returned when the operation fails. """ def __init__( @@ -1635,6 +1636,7 @@ def __init__( resource_id: str, *, is_error: Optional[bool] = None, + message: Optional[str] = None, ) -> None: """ Initialize a TagResultsItem object. @@ -1642,9 +1644,11 @@ def __init__( :param str resource_id: The CRN or IMS ID of the resource. :param bool is_error: (optional) It is `true` if the operation exits with an error. + :param str message: (optional) Error message returned when the operation fails. """ self.resource_id = resource_id self.is_error = is_error + self.message = message @classmethod def from_dict(cls, _dict: Dict) -> 'TagResultsItem': @@ -1656,6 +1660,8 @@ def from_dict(cls, _dict: Dict) -> 'TagResultsItem': raise ValueError('Required property \'resource_id\' not present in TagResultsItem JSON') if (is_error := _dict.get('is_error')) is not None: args['is_error'] = is_error + if (message := _dict.get('message')) is not None: + args['message'] = message return cls(**args) @classmethod From 9ed61e30e8e3f4ee3447103e67c4f6e60a1bfa35 Mon Sep 17 00:00:00 2001 From: Stefano Roberto Mollica Date: Mon, 16 Feb 2026 10:34:47 +0100 Subject: [PATCH 2/4] feat(Tagging): added error message in case of failures Signed-off-by: Stefano Roberto Mollica --- ibm_platform_services/global_tagging_v1.py | 24 ++++++++-------- test/unit/test_global_tagging_v1.py | 32 ++++++++++------------ 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/ibm_platform_services/global_tagging_v1.py b/ibm_platform_services/global_tagging_v1.py index 7b25934..77524d0 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. 2025. +# (C) Copyright IBM Corp. 2026. # # 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.105.0-3c13b041-20250605-193116 +# IBM OpenAPI SDK Code Generator Version: 3.111.0-1bfb72c2-20260206-185521 """ Manage your tags with the Tagging API in IBM Cloud. You can attach, detach, delete, or @@ -63,7 +63,9 @@ def new_instance( parameters and external configuration. """ authenticator = get_authenticator_from_environment(service_name) - service = cls(authenticator) + service = cls( + authenticator + ) service.configure_service(service_name) return service @@ -680,7 +682,6 @@ class TagType(str, Enum): USER = 'user' SERVICE = 'service' ACCESS = 'access' - class Providers(str, Enum): """ Select a provider. Supported values are `ghost` and `ims`. To list both Global @@ -691,7 +692,6 @@ class Providers(str, Enum): GHOST = 'ghost' IMS = 'ims' - class OrderByName(str, Enum): """ Order the output by tag name. @@ -726,7 +726,6 @@ class Providers(str, Enum): GHOST = 'ghost' IMS = 'ims' - class TagType(str, Enum): """ The type of the tag. Supported values are `user`, `service` and `access`. @@ -752,7 +751,6 @@ class Providers(str, Enum): GHOST = 'ghost' IMS = 'ims' - class TagType(str, Enum): """ The type of the tag. Supported values are `user`, `service` and `access`. @@ -1051,9 +1049,9 @@ def from_dict(cls, _dict: Dict) -> 'DeleteTagResultsItem': args['is_error'] = is_error 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 + 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 @@ -1118,6 +1116,7 @@ class ProviderEnum(str, Enum): IMS = 'ims' + class DeleteTagsResult: """ Results of deleting unattatched tags. @@ -1644,7 +1643,8 @@ def __init__( :param str resource_id: The CRN or IMS ID of the resource. :param bool is_error: (optional) It is `true` if the operation exits with an error. - :param str message: (optional) Error message returned when the operation fails. + :param str message: (optional) Error message returned when the operation + fails. """ self.resource_id = resource_id self.is_error = is_error @@ -1676,6 +1676,8 @@ def to_dict(self) -> Dict: _dict['resource_id'] = self.resource_id if hasattr(self, 'is_error') and self.is_error is not None: _dict['is_error'] = self.is_error + if hasattr(self, 'message') and self.message is not None: + _dict['message'] = self.message return _dict def _to_dict(self): diff --git a/test/unit/test_global_tagging_v1.py b/test/unit/test_global_tagging_v1.py index 2f34f5d..c1bde22 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. 2025. +# (C) Copyright IBM Corp. 2026. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -29,7 +29,9 @@ from ibm_platform_services.global_tagging_v1 import * -_service = GlobalTaggingV1(authenticator=NoAuthAuthenticator()) +_service = GlobalTaggingV1( + authenticator=NoAuthAuthenticator() +) _base_url = 'https://tags.global-search-tagging.cloud.ibm.com' _service.set_service_url(_base_url) @@ -573,7 +575,7 @@ def test_attach_tag_all_params(self): """ # Set up mock url = preprocess_url('/v3/tags/attach') - mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true}]}' + mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true, "message": "message"}]}' responses.add( responses.POST, url, @@ -651,7 +653,7 @@ def test_attach_tag_required_params(self): """ # Set up mock url = preprocess_url('/v3/tags/attach') - mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true}]}' + mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true, "message": "message"}]}' responses.add( responses.POST, url, @@ -716,7 +718,7 @@ def test_detach_tag_all_params(self): """ # Set up mock url = preprocess_url('/v3/tags/detach') - mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true}]}' + mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true, "message": "message"}]}' responses.add( responses.POST, url, @@ -788,7 +790,7 @@ def test_detach_tag_required_params(self): """ # Set up mock url = preprocess_url('/v3/tags/detach') - mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true}]}' + mock_response = '{"results": [{"resource_id": "resource_id", "is_error": true, "message": "message"}]}' responses.add( responses.POST, url, @@ -905,18 +907,12 @@ def test_create_tag_results_results_item_serialization(self): create_tag_results_results_item_model_json['is_error'] = True # Construct a model instance of CreateTagResultsResultsItem by calling from_dict on the json representation - create_tag_results_results_item_model = CreateTagResultsResultsItem.from_dict( - create_tag_results_results_item_model_json - ) + create_tag_results_results_item_model = CreateTagResultsResultsItem.from_dict(create_tag_results_results_item_model_json) assert create_tag_results_results_item_model != False # Construct a model instance of CreateTagResultsResultsItem by calling from_dict on the json representation - create_tag_results_results_item_model_dict = CreateTagResultsResultsItem.from_dict( - create_tag_results_results_item_model_json - ).__dict__ - create_tag_results_results_item_model2 = CreateTagResultsResultsItem( - **create_tag_results_results_item_model_dict - ) + create_tag_results_results_item_model_dict = CreateTagResultsResultsItem.from_dict(create_tag_results_results_item_model_json).__dict__ + create_tag_results_results_item_model2 = CreateTagResultsResultsItem(**create_tag_results_results_item_model_dict) # Verify the model instances are equivalent assert create_tag_results_results_item_model == create_tag_results_results_item_model2 @@ -1216,10 +1212,9 @@ def test_tag_results_serialization(self): # Construct dict forms of any model objects needed in order to build this model. tag_results_item_model = {} # TagResultsItem - tag_results_item_model['resource_id'] = ( - 'crn:v1:staging:public:resource-controller::a/5c2ac0d93c69e82c6c9c7c78dc4beda3::resource-group:1c061f4485b34360a8f8ee049880dc13' - ) + tag_results_item_model['resource_id'] = 'crn:v1:staging:public:resource-controller::a/5c2ac0d93c69e82c6c9c7c78dc4beda3::resource-group:1c061f4485b34360a8f8ee049880dc13' tag_results_item_model['is_error'] = False + tag_results_item_model['message'] = 'testString' # Construct a json representation of a TagResults model tag_results_model_json = {} @@ -1255,6 +1250,7 @@ def test_tag_results_item_serialization(self): tag_results_item_model_json = {} tag_results_item_model_json['resource_id'] = 'testString' tag_results_item_model_json['is_error'] = True + tag_results_item_model_json['message'] = 'testString' # Construct a model instance of TagResultsItem by calling from_dict on the json representation tag_results_item_model = TagResultsItem.from_dict(tag_results_item_model_json) From 8b8bc25d4b9e520f0cdf0ad5d17deb1e27219ef1 Mon Sep 17 00:00:00 2001 From: Stefano Roberto Mollica Date: Tue, 17 Feb 2026 11:08:35 +0100 Subject: [PATCH 3/4] feat(Tagging): regenerated services with latest API definition Signed-off-by: Stefano Roberto Mollica --- ibm_platform_services/global_tagging_v1.py | 15 ++++++++------- test/unit/test_global_tagging_v1.py | 20 +++++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/ibm_platform_services/global_tagging_v1.py b/ibm_platform_services/global_tagging_v1.py index 77524d0..6b23097 100644 --- a/ibm_platform_services/global_tagging_v1.py +++ b/ibm_platform_services/global_tagging_v1.py @@ -63,9 +63,7 @@ def new_instance( parameters and external configuration. """ authenticator = get_authenticator_from_environment(service_name) - service = cls( - authenticator - ) + service = cls(authenticator) service.configure_service(service_name) return service @@ -682,6 +680,7 @@ class TagType(str, Enum): USER = 'user' SERVICE = 'service' ACCESS = 'access' + class Providers(str, Enum): """ Select a provider. Supported values are `ghost` and `ims`. To list both Global @@ -692,6 +691,7 @@ class Providers(str, Enum): GHOST = 'ghost' IMS = 'ims' + class OrderByName(str, Enum): """ Order the output by tag name. @@ -726,6 +726,7 @@ class Providers(str, Enum): GHOST = 'ghost' IMS = 'ims' + class TagType(str, Enum): """ The type of the tag. Supported values are `user`, `service` and `access`. @@ -751,6 +752,7 @@ class Providers(str, Enum): GHOST = 'ghost' IMS = 'ims' + class TagType(str, Enum): """ The type of the tag. Supported values are `user`, `service` and `access`. @@ -1049,9 +1051,9 @@ def from_dict(cls, _dict: Dict) -> 'DeleteTagResultsItem': args['is_error'] = is_error 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 + 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 @@ -1116,7 +1118,6 @@ class ProviderEnum(str, Enum): IMS = 'ims' - class DeleteTagsResult: """ Results of deleting unattatched tags. diff --git a/test/unit/test_global_tagging_v1.py b/test/unit/test_global_tagging_v1.py index c1bde22..7a39e2c 100644 --- a/test/unit/test_global_tagging_v1.py +++ b/test/unit/test_global_tagging_v1.py @@ -29,9 +29,7 @@ from ibm_platform_services.global_tagging_v1 import * -_service = GlobalTaggingV1( - authenticator=NoAuthAuthenticator() -) +_service = GlobalTaggingV1(authenticator=NoAuthAuthenticator()) _base_url = 'https://tags.global-search-tagging.cloud.ibm.com' _service.set_service_url(_base_url) @@ -907,12 +905,18 @@ def test_create_tag_results_results_item_serialization(self): create_tag_results_results_item_model_json['is_error'] = True # Construct a model instance of CreateTagResultsResultsItem by calling from_dict on the json representation - create_tag_results_results_item_model = CreateTagResultsResultsItem.from_dict(create_tag_results_results_item_model_json) + create_tag_results_results_item_model = CreateTagResultsResultsItem.from_dict( + create_tag_results_results_item_model_json + ) assert create_tag_results_results_item_model != False # Construct a model instance of CreateTagResultsResultsItem by calling from_dict on the json representation - create_tag_results_results_item_model_dict = CreateTagResultsResultsItem.from_dict(create_tag_results_results_item_model_json).__dict__ - create_tag_results_results_item_model2 = CreateTagResultsResultsItem(**create_tag_results_results_item_model_dict) + create_tag_results_results_item_model_dict = CreateTagResultsResultsItem.from_dict( + create_tag_results_results_item_model_json + ).__dict__ + create_tag_results_results_item_model2 = CreateTagResultsResultsItem( + **create_tag_results_results_item_model_dict + ) # Verify the model instances are equivalent assert create_tag_results_results_item_model == create_tag_results_results_item_model2 @@ -1212,7 +1216,9 @@ def test_tag_results_serialization(self): # Construct dict forms of any model objects needed in order to build this model. tag_results_item_model = {} # TagResultsItem - tag_results_item_model['resource_id'] = 'crn:v1:staging:public:resource-controller::a/5c2ac0d93c69e82c6c9c7c78dc4beda3::resource-group:1c061f4485b34360a8f8ee049880dc13' + tag_results_item_model['resource_id'] = ( + 'crn:v1:staging:public:resource-controller::a/5c2ac0d93c69e82c6c9c7c78dc4beda3::resource-group:1c061f4485b34360a8f8ee049880dc13' + ) tag_results_item_model['is_error'] = False tag_results_item_model['message'] = 'testString' From 58436a30db015f310d044cb3912833c5a671d846 Mon Sep 17 00:00:00 2001 From: Stefano Roberto Mollica Date: Wed, 18 Feb 2026 16:43:59 +0100 Subject: [PATCH 4/4] feat(Tagging): regenerated services with latest API definition Signed-off-by: Stefano Roberto Mollica --- examples/test_global_tagging_v1_examples.py | 8 ++++++-- test/integration/test_global_tagging_v1.py | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/test_global_tagging_v1_examples.py b/examples/test_global_tagging_v1_examples.py index 52499ab..8633764 100644 --- a/examples/test_global_tagging_v1_examples.py +++ b/examples/test_global_tagging_v1_examples.py @@ -128,8 +128,10 @@ 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( - tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' + resources=[resource_model], tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' ).get_result() print(json.dumps(tag_results, indent=2)) @@ -148,8 +150,10 @@ 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( - tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' + resources=[resource_model], tag_names=['tag_test_1', 'tag_test_2'], tag_type='user' ).get_result() print(json.dumps(tag_results, indent=2)) diff --git a/test/integration/test_global_tagging_v1.py b/test/integration/test_global_tagging_v1.py index f2bb8af..3c3cc64 100644 --- a/test/integration/test_global_tagging_v1.py +++ b/test/integration/test_global_tagging_v1.py @@ -329,7 +329,7 @@ def test_delete_tag_user(self): print('\ndelete_tag(user) result: ', json.dumps(delete_tag_results.to_dict(), indent=2)) for item in delete_tag_results.results: - assert item.is_error is False + assert item.is_error is True # tags are already deleted at detach time if not attached to any resource @needscredentials def test_delete_tag_access(self):