From 325444475b14577734d36b238241996eb5e74056 Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:45:27 +0530 Subject: [PATCH 1/2] Implemented variants support --- CHANGELOG.md | 7 + contentstack_management/__init__.py | 10 +- contentstack_management/entries/entry.py | 56 +++- .../entry_variants/__init__.py | 3 + .../entry_variants/entry_variants.py | 272 ++++++++++++++++++ contentstack_management/stack/stack.py | 10 +- .../variant_group/__init__.py | 1 + .../variant_group/variant_group.py | 221 ++++++++++++++ contentstack_management/variants/__init__.py | 3 + contentstack_management/variants/variants.py | 256 +++++++++++++++++ tests/__init__.py | 1 + tests/cred.py | 6 +- tests/unit/entry_variants/__init__.py | 1 + .../entry_variants/test_entry_variants.py | 169 +++++++++++ tests/unit/variant_group/__init__.py | 1 + .../unit/variant_group/test_variant_group.py | 115 ++++++++ tests/unit/variants/__init__.py | 1 + tests/unit/variants/test_variants.py | 178 ++++++++++++ 18 files changed, 1302 insertions(+), 9 deletions(-) create mode 100644 contentstack_management/entry_variants/__init__.py create mode 100644 contentstack_management/entry_variants/entry_variants.py create mode 100644 contentstack_management/variant_group/__init__.py create mode 100644 contentstack_management/variant_group/variant_group.py create mode 100644 contentstack_management/variants/__init__.py create mode 100644 contentstack_management/variants/variants.py create mode 100644 tests/unit/entry_variants/__init__.py create mode 100644 tests/unit/entry_variants/test_entry_variants.py create mode 100644 tests/unit/variant_group/__init__.py create mode 100644 tests/unit/variant_group/test_variant_group.py create mode 100644 tests/unit/variants/__init__.py create mode 100644 tests/unit/variants/test_variants.py diff --git a/CHANGELOG.md b/CHANGELOG.md index e1a86c0..908e001 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # CHANGELOG ## Content Management SDK For Python +--- +## v1.5.0 + +#### Date: 25 August 2025 + +- Variants feature support. + --- ## v1.4.0 diff --git a/contentstack_management/__init__.py b/contentstack_management/__init__.py index 849cd80..a5a380a 100644 --- a/contentstack_management/__init__.py +++ b/contentstack_management/__init__.py @@ -16,6 +16,7 @@ from .auditlogs.auditlog import Auditlog from .environments.environment import Environment from .entries.entry import Entry +from .entry_variants.entry_variants import EntryVariants from .contentstack import Client, Region from ._api_client import _APIClient from .common import Parameter @@ -31,6 +32,8 @@ from .management_token.management_token import ManagementToken from .publish_queue.publish_queue import PublishQueue from .extensions.extension import Extension +from .variant_group.variant_group import VariantGroup +from .variants.variants import Variants __all__ = ( @@ -55,6 +58,7 @@ "Auditlog", "Environment", "Entry", +"EntryVariants", "Locale", "Taxonomy", "Label", @@ -65,14 +69,16 @@ "DeliveryToken", "ManagementToken", "PublishQueue", -"Extension" +"Extension", +"VariantGroup", +"Variants" ) __title__ = 'contentstack-management-python' __author__ = 'dev-ex' __status__ = 'debug' __region__ = 'na' -__version__ = '1.4.0' +__version__ = '1.5.0' __host__ = 'api.contentstack.io' __protocol__ = 'https://' __api_version__ = 'v3' diff --git a/contentstack_management/entries/entry.py b/contentstack_management/entries/entry.py index 25b01e9..b56377f 100644 --- a/contentstack_management/entries/entry.py +++ b/contentstack_management/entries/entry.py @@ -5,6 +5,7 @@ import json from ..common import Parameter +from ..entry_variants.entry_variants import EntryVariants class Entry(Parameter): """ @@ -421,13 +422,58 @@ def unpublish(self, data): data = json.dumps(data) return self.client.post(url, headers = self.client.headers, data = data, params = self.params) - - - - + def variants(self, variant_uid: str = None): + """ + Returns an EntryVariants instance for managing variant entries. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant. It is used to specify which variant to work with + :type variant_uid: str + :return: EntryVariants instance for managing variant entries + ------------------------------- + [Example:] - + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # Get all variant entries + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().query().find().json() + >>> # Get specific variant entry + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').fetch().json() + ------------------------------- + """ + + return EntryVariants(self.client, self.content_type_uid, self.entry_uid, variant_uid) + def includeVariants(self, include_variants: str = 'true', variant_uid: str = None, params: dict = None): + """ + The includeVariants method retrieves the details of a specific base entry with variant details. + + :param include_variants: The `include_variants` parameter is a string that specifies whether to include variants + :type include_variants: str + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant. It is used to specify which variant to include + :type variant_uid: str + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: the result of the GET request made to the specified URL. + ------------------------------- + [Example:] + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').includeVariants('true', 'variant_uid').json() + >>> # With parameters + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').includeVariants('true', 'variant_uid', params={'locale': 'en-us'}).json() + ------------------------------- + """ + if self.entry_uid is None: + raise Exception('Entry uid is required') + if params is not None: + self.params.update(params) + self.params['include_variants'] = include_variants + if variant_uid is not None and variant_uid != '': + self.params['variant_uid'] = variant_uid + url = f"content_types/{self.content_type_uid}/entries/{self.entry_uid}" + return self.client.get(url, headers = self.client.headers, params = self.params) \ No newline at end of file diff --git a/contentstack_management/entry_variants/__init__.py b/contentstack_management/entry_variants/__init__.py new file mode 100644 index 0000000..218bf90 --- /dev/null +++ b/contentstack_management/entry_variants/__init__.py @@ -0,0 +1,3 @@ +from .entry_variants import EntryVariants + +__all__ = ['EntryVariants'] diff --git a/contentstack_management/entry_variants/entry_variants.py b/contentstack_management/entry_variants/entry_variants.py new file mode 100644 index 0000000..b2487a9 --- /dev/null +++ b/contentstack_management/entry_variants/entry_variants.py @@ -0,0 +1,272 @@ +"""This class takes a base URL as an argument when it's initialized, +which is the endpoint for the RESTFUL API that we'll be interacting with. +The query(), create(), fetch(), delete(), update(), versions(), and includeVariants() methods each correspond to +the operations that can be performed on the API """ + +import json +from ..common import Parameter +from .._errors import ArgumentException + +class EntryVariants(Parameter): + """ + This class takes a base URL as an argument when it's initialized, + which is the endpoint for the RESTFUL API that + we'll be interacting with. The query(), create(), fetch(), delete(), update(), versions(), and includeVariants() + methods each correspond to the operations that can be performed on the API """ + + def __init__(self, client, content_type_uid: str, entry_uid: str, variant_uid: str = None): + self.client = client + self.content_type_uid = content_type_uid + self.entry_uid = entry_uid + self.variant_uid = variant_uid + super().__init__(self.client) + self.path = f"content_types/{content_type_uid}/entries/{entry_uid}/variants" + + + def find(self, params: dict = None): + """ + The Find variant entries call fetches all the existing variant customizations for an entry. + + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: Json, with variant entry details. + + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack("api_key").content_types('content_type_uid').entry('entry_uid').variants().query().find().json() + >>> # With parameters + >>> result = client.stack("api_key").content_types('content_type_uid').entry('entry_uid').variants().find({'limit': 10, 'skip': 0}).json() + + ------------------------------- + """ + self.validate_content_type_uid() + self.validate_entry_uid() + if params is not None: + self.params.update(params) + return self.client.get(self.path, headers = self.client.headers, params = self.params) + + def create(self, data: dict): + """ + This call is used to create a variant entry for an entry. + + :param data: The `data` parameter is the payload that you want to send in the request body. It + should be a dictionary or a JSON serializable object that you want to send as the request body + :return: Json, with variant entry details. + + ------------------------------- + [Example:] + >>> data = { + >>> "customized_fields": [ + >>> "title", + >>> "url" + >>> ], + >>> "base_entry_version": 10, # optional + >>> "entry": { + >>> "title": "example", + >>> "url": "/example" + >>> } + >>> } + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().create(data).json() + + ------------------------------- + """ + self.validate_content_type_uid() + self.validate_entry_uid() + data = json.dumps(data) + return self.client.post(self.path, headers = self.client.headers, data=data, params = self.params) + + def fetch(self, variant_uid: str = None, params: dict = None): + """ + The fetch Variant entry call fetches variant entry details. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + a variant. It is used to specify which variant to fetch from the server + :type variant_uid: str + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: the result of the GET request made to the specified URL. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').fetch().json() + >>> # With parameters + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').fetch(params={'include_count': True}).json() + + ------------------------------- + """ + + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + + self.validate_content_type_uid() + self.validate_entry_uid() + self.validate_variant_uid() + if params is not None: + self.params.update(params) + url = f"{self.path}/{self.variant_uid}" + return self.client.get(url, headers = self.client.headers, params = self.params) + + def delete(self, variant_uid: str = None): + """ + The delete a variant entry call is used to delete a specific variant entry. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant that you want to delete + :type variant_uid: str + :return: the result of the `client.delete()` method, which is likely a response object or a + boolean value indicating the success of the deletion operation. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').delete().json() + + ------------------------------- + """ + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + self.validate_content_type_uid() + self.validate_entry_uid() + self.validate_variant_uid() + url = f"{self.path}/{self.variant_uid}" + return self.client.delete(url, headers = self.client.headers, params = self.params) + + def update(self, data: dict, variant_uid: str = None): + """ + The update a variant entry call updates an entry of a selected variant entry. + + :param data: The `data` parameter is a dictionary that contains the updated information that you + want to send to the server. This data will be converted to a JSON string before sending it in + the request + :type data: dict + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant. It is used to specify which variant should be updated with the provided data + :type variant_uid: str + :return: the result of the `put` request made to the specified URL. + ------------------------------- + [Example:] + >>> data = { + >>> "customized_fields": [ + >>> "title", + >>> "url" + >>> ], + >>> "base_entry_version": 10, # optional + >>> "entry": { + >>> "title": "example", + >>> "url": "/example" + >>> } + >>> } + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').update(data).json() + + ------------------------------- + """ + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + self.validate_content_type_uid() + self.validate_entry_uid() + self.validate_variant_uid() + url = f"{self.path}/{self.variant_uid}" + data = json.dumps(data) + return self.client.put(url, headers = self.client.headers, data=data, params = self.params) + + def versions(self, variant_uid: str = None, params: dict = None): + """ + The version method retrieves the details of a specific variant entry version details. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant. It is used to specify which variant to get versions for + :type variant_uid: str + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: the result of the GET request made to the specified URL. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').versions().json() + >>> # With parameters + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').versions(params={'limit': 10}).json() + + ------------------------------- + """ + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + self.validate_content_type_uid() + self.validate_entry_uid() + self.validate_variant_uid() + if params is not None: + self.params.update(params) + url = f"{self.path}/{self.variant_uid}/versions" + return self.client.get(url, headers = self.client.headers, params = self.params) + + def includeVariants(self, include_variants: str = 'true', variant_uid: str = None, params: dict = None): + """ + The includeVariants method retrieves the details of a specific base entry with variant details. + + :param include_variants: The `include_variants` parameter is a string that specifies whether to include variants + :type include_variants: str + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant. It is used to specify which variant to include + :type variant_uid: str + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: the result of the GET request made to the specified URL. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').includeVariants('true', 'variant_uid').json() + >>> # With parameters + >>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').includeVariants('true', 'variant_uid', params={'locale': 'en-us'}).json() + + ------------------------------- + """ + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + self.validate_content_type_uid() + self.validate_entry_uid() + self.validate_variant_uid() + if params is not None: + self.params.update(params) + self.params['include_variants'] = include_variants + url = f"content_types/{self.content_type_uid}/entries/{self.entry_uid}" + return self.client.get(url, headers = self.client.headers, params = self.params) + + def validate_content_type_uid(self): + """ + The function checks if the content_type_uid is None or an empty string and raises an ArgumentException + if it is. + """ + + if self.content_type_uid is None or self.content_type_uid == '': + raise ArgumentException("content type Uid is required") + + def validate_entry_uid(self): + """ + The function checks if the entry_uid is None or an empty string and raises an ArgumentException + if it is. + """ + + if self.entry_uid is None or self.entry_uid == '': + raise ArgumentException("entry Uid is required") + + def validate_variant_uid(self): + """ + The function checks if the variant_uid is None or an empty string and raises an ArgumentException + if it is. + """ + + if self.variant_uid is None or self.variant_uid == '': + raise ArgumentException("variant Uid is required") diff --git a/contentstack_management/stack/stack.py b/contentstack_management/stack/stack.py index 8fe4ee5..6ef142f 100644 --- a/contentstack_management/stack/stack.py +++ b/contentstack_management/stack/stack.py @@ -20,6 +20,8 @@ from ..management_token.management_token import ManagementToken from ..publish_queue.publish_queue import PublishQueue from ..extensions.extension import Extension +from ..variant_group.variant_group import VariantGroup +from ..variants.variants import Variants class Stack(Parameter): @@ -351,4 +353,10 @@ def publish_queue(self, publish_queue_uid: str = None): return PublishQueue(self.client, publish_queue_uid) def extension(self, extension_uid: str = None): - return Extension(self.client, extension_uid) \ No newline at end of file + return Extension(self.client, extension_uid) + + def variant_group(self, variant_group_uid: str = None): + return VariantGroup(self.client, variant_group_uid) + + def variants(self, variant_uid: str = None): + return Variants(self.client, None, variant_uid) \ No newline at end of file diff --git a/contentstack_management/variant_group/__init__.py b/contentstack_management/variant_group/__init__.py new file mode 100644 index 0000000..ce6b0cb --- /dev/null +++ b/contentstack_management/variant_group/__init__.py @@ -0,0 +1 @@ +import contentstack_management \ No newline at end of file diff --git a/contentstack_management/variant_group/variant_group.py b/contentstack_management/variant_group/variant_group.py new file mode 100644 index 0000000..f57d67b --- /dev/null +++ b/contentstack_management/variant_group/variant_group.py @@ -0,0 +1,221 @@ +"""This class takes a base URL as an argument when it's initialized, +which is the endpoint for the RESTFUL API that we'll be interacting with. +The create(), read(), update(), and delete() methods each correspond to +the CRUD operations that can be performed on the API """ + +import json +from ..common import Parameter +from .._errors import ArgumentException +from ..variants.variants import Variants + +class VariantGroup(Parameter): + """ + This class takes a base URL as an argument when it's initialized, + which is the endpoint for the RESTFUL API that + we'll be interacting with. The create(), read(), update(), and delete() + methods each correspond to the CRUD + operations that can be performed on the API """ + + def __init__(self, client, variant_group_uid: str = None): + self.client = client + self.variant_group_uid = variant_group_uid + super().__init__(self.client) + self.path = "variant_groups" + + def find(self): + """ + The Find variant group call fetches all the existing variant groups of the stack. + :return: Json, with variant group details. + + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack("api_key").variant_group().find().json() + + ------------------------------- + """ + return self.client.get(self.path, headers = self.client.headers, params = self.params) + + def query(self, query_params: dict = None): + """ + The Query on variant group will allow to fetch details of all or specific variant groups with filtering. + + :param query_params: The `query_params` parameter is a dictionary that contains query parameters for filtering + :type query_params: dict + :return: Json, with filtered variant group details. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack("api_key").variant_group().query({'name': 'Colors'}).find().json() + + ------------------------------- + """ + if query_params is not None: + self.params.update(query_params) + return self + + def fetch(self, variant_group_uid: str = None): + """ + The Get variant group call returns information about a particular variant group of a stack. + + :param variant_group_uid: The `variant_group_uid` parameter is a string that represents the unique identifier of + a variant group. It is used to specify which variant group to fetch from the server + :type variant_group_uid: str + :return: the result of the GET request made to the specified URL. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').variant_group('variant_group_uid').fetch().json() + + ------------------------------- + """ + + if variant_group_uid is not None and variant_group_uid != '': + self.variant_group_uid = variant_group_uid + + self.validate_uid() + url = f"{self.path}/{self.variant_group_uid}" + return self.client.get(url, headers = self.client.headers, params = self.params) + + def create(self, data: dict): + """ + This call is used to create a variant group. + + :param data: The `data` parameter is the payload that you want to send in the request body. It + should be a dictionary or a JSON serializable object that you want to send as the request body + :return: Json, with variant group details. + + ------------------------------- + [Example:] + >>> data = { + >>> "name": "Colors", + >>> "content_types": [ + >>> "iphone_product_page" + >>> ], + >>> "uid": "iphone_color_white" # optional + >>> } + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').variant_group().create(data).json() + + ------------------------------- + """ + + data = json.dumps(data) + return self.client.post(self.path, headers = self.client.headers, data=data, params = self.params) + + def update(self, data: dict, variant_group_uid: str = None): + """ + The "Update variant group" call is used to update an existing variant group. + + :param data: The `data` parameter is a dictionary that contains the updated information that you + want to send to the server. This data will be converted to a JSON string before sending it in + the request + :type data: dict + :param variant_group_uid: The `variant_group_uid` parameter is a string that represents the unique identifier of + the variant group. It is used to specify which variant group should be updated with the provided data + :type variant_group_uid: str + :return: the result of the `put` request made to the specified URL. + ------------------------------- + [Example:] + >>> data = { + >>> "name": "iPhone Colors", + >>> "content_types": [ + >>> {"uid": "iphone_product_page", "status": "linked"} + >>> ], + >>> "personalize_metadata": { + >>> "experience_uid": "variant_group_ex_uid_update", + >>> "experience_short_uid": "variant_group_short_uid_update", + >>> "project_uid": "variant_group_project_uid_update", + >>> "status": "linked" + >>> } + >>> } + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').variant_group("variant_group_uid").update(data).json() + + ------------------------------- + """ + if variant_group_uid is not None and variant_group_uid != '': + self.variant_group_uid = variant_group_uid + self.validate_uid() + url = f"{self.path}/{self.variant_group_uid}" + data = json.dumps(data) + return self.client.put(url, headers = self.client.headers, data=data, params = self.params) + + def delete(self, variant_group_uid: str = None): + """ + The "Delete variant group" call is used to delete a specific variant group. + + :param variant_group_uid: The `variant_group_uid` parameter is a string that represents the unique identifier of + the variant group that you want to delete + :type variant_group_uid: str + :return: the result of the `client.delete()` method, which is likely a response object or a + boolean value indicating the success of the deletion operation. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> result = client.stack('api_key').variant_group('variant_group_uid').delete().json() + + ------------------------------- + """ + if variant_group_uid is not None and variant_group_uid != '': + self.variant_group_uid = variant_group_uid + self.validate_uid() + url = f"{self.path}/{self.variant_group_uid}" + return self.client.delete(url, headers = self.client.headers, params = self.params) + + def link_contenttypes(self, data: dict, variant_group_uid: str = None): + if variant_group_uid is not None and variant_group_uid != '': + self.variant_group_uid = variant_group_uid + self.validate_uid() + url = f"{self.path}/{self.variant_group_uid}" + data = json.dumps(data) + return self.client.put(url, headers = self.client.headers, data=data, params = self.params) + + def unlink_contenttypes(self, data: dict, variant_group_uid: str = None): + if variant_group_uid is not None and variant_group_uid != '': + self.variant_group_uid = variant_group_uid + self.validate_uid() + url = f"{self.path}/{self.variant_group_uid}" + data = json.dumps(data) + return self.client.put(url, headers = self.client.headers, data=data, params = self.params) + + def variants(self, variant_uid: str = None): + """ + Returns a Variants instance for managing variants within this variant group. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + a variant. It is used to specify which variant to work with + :type variant_uid: str + :return: Variants instance for managing variants + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # Get all variants + >>> result = client.stack('api_key').variant_group('variant_group_uid').variants().find().json() + >>> # Get specific variant + >>> result = client.stack('api_key').variant_group('variant_group_uid').variants('variant_uid').fetch().json() + + ------------------------------- + """ + return Variants(self.client, self.variant_group_uid, variant_uid) + + def validate_uid(self): + """ + The function checks if the variant_group_uid is None or an empty string and raises an ArgumentException + if it is. + """ + + if self.variant_group_uid is None or self.variant_group_uid == '': + raise ArgumentException("variant group Uid is required") \ No newline at end of file diff --git a/contentstack_management/variants/__init__.py b/contentstack_management/variants/__init__.py new file mode 100644 index 0000000..25920d2 --- /dev/null +++ b/contentstack_management/variants/__init__.py @@ -0,0 +1,3 @@ +from .variants import Variants + +__all__ = ['Variants'] \ No newline at end of file diff --git a/contentstack_management/variants/variants.py b/contentstack_management/variants/variants.py new file mode 100644 index 0000000..b19a8d7 --- /dev/null +++ b/contentstack_management/variants/variants.py @@ -0,0 +1,256 @@ +"""This class takes a base URL as an argument when it's initialized, +which is the endpoint for the RESTFUL API that we'll be interacting with. +The create(), read(), update(), and delete() methods each correspond to +the CRUD operations that can be performed on the API """ + +import json +from ..common import Parameter +from .._errors import ArgumentException + +class Variants(Parameter): + """ + This class takes a base URL as an argument when it's initialized, + which is the endpoint for the RESTFUL API that + we'll be interacting with. The create(), read(), update(), and delete() + methods each correspond to the CRUD + operations that can be performed on the API """ + + def __init__(self, client, variant_group_uid: str = None, variant_uid: str = None): + self.client = client + self.variant_group_uid = variant_group_uid + self.variant_uid = variant_uid + super().__init__(self.client) + if self.variant_group_uid: + self.path = f"variant_groups/{self.variant_group_uid}/variants" + else: + self.path = "variants" + + def find(self, params: dict = None): + """ + The Find variants call fetches all the existing variants of a variant group or ungrouped variants. + + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: Json, with variant details. + + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants + >>> result = client.stack("api_key").variant_group('variant_group_uid').variants().find().json() + >>> # For ungrouped variants + >>> result = client.stack("api_key").variants().find().json() + >>> # With parameters + >>> result = client.stack("api_key").variants().find({'limit': 10, 'skip': 0}).json() + + ------------------------------- + """ + if self.variant_group_uid: + self.validate_variant_group_uid() + if params is not None: + self.params.update(params) + return self.client.get(self.path, headers = self.client.headers, params = self.params) + + def query(self, query_params: dict = None): + """ + The Query on variants will allow to fetch details of all or specific variants with filtering. + + :param query_params: The `query_params` parameter is a dictionary that contains query parameters for filtering + :type query_params: dict + :return: Json, with filtered variant details. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants with query + >>> result = client.stack("api_key").variant_group('variant_group_uid').variants().query({'title': 'variant title'}).find().json() + >>> # For ungrouped variants with query + >>> result = client.stack("api_key").variants().query({'title': 'variant title'}).find().json() + + ------------------------------- + """ + if query_params is not None: + self.params.update(query_params) + return self + + def fetch(self, variant_uid: str = None, params: dict = None): + """ + The Get variant call returns information about a particular variant of a variant group or ungrouped variant. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + a variant. It is used to specify which variant to fetch from the server + :type variant_uid: str + :param params: The `params` parameter is a dictionary that contains query parameters to be sent with the request + :type params: dict + :return: the result of the GET request made to the specified URL. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants + >>> result = client.stack('api_key').variant_group('variant_group_uid').variants('variant_uid').fetch().json() + >>> # For ungrouped variants + >>> result = client.stack('api_key').variants('variant_uid').fetch().json() + >>> # With parameters + >>> result = client.stack('api_key').variants('variant_uid').fetch(params={'include_count': True}).json() + + ------------------------------- + """ + + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + + if self.variant_group_uid: + self.validate_variant_group_uid() + self.validate_variant_uid() + if params is not None: + self.params.update(params) + url = f"{self.path}/{self.variant_uid}" + return self.client.get(url, headers = self.client.headers, params = self.params) + + def create(self, data: dict): + """ + This call is used to create a variant within a variant group or as an ungrouped variant. + + :param data: The `data` parameter is the payload that you want to send in the request body. It + should be a dictionary or a JSON serializable object that you want to send as the request body + :return: Json, with variant details. + + ------------------------------- + [Example:] + >>> data = { + >>> "uid": "iphone_color_white", # optional + >>> "name": "White" + >>> } + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants + >>> result = client.stack('api_key').variant_group('variant_group_uid').variants().create(data).json() + >>> # For ungrouped variants + >>> result = client.stack('api_key').variants().create(data).json() + + ------------------------------- + """ + if self.variant_group_uid: + self.validate_variant_group_uid() + data = json.dumps(data) + return self.client.post(self.path, headers = self.client.headers, data=data, params = self.params) + + def update(self, data: dict, variant_uid: str = None): + """ + The "Update variant" call is used to update an existing variant. + + :param data: The `data` parameter is a dictionary that contains the updated information that you + want to send to the server. This data will be converted to a JSON string before sending it in + the request + :type data: dict + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant. It is used to specify which variant should be updated with the provided data + :type variant_uid: str + :return: the result of the `put` request made to the specified URL. + ------------------------------- + [Example:] + >>> data = { + >>> "name": "updated name" + >>> } + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants + >>> result = client.stack('api_key').variant_group("variant_group_uid").variants('variant_uid').update(data).json() + >>> # For ungrouped variants + >>> result = client.stack('api_key').variants('variant_uid').update(data).json() + + ------------------------------- + """ + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + if self.variant_group_uid: + self.validate_variant_group_uid() + self.validate_variant_uid() + url = f"{self.path}/{self.variant_uid}" + data = json.dumps(data) + return self.client.put(url, headers = self.client.headers, data=data, params = self.params) + + def delete(self, variant_uid: str = None): + """ + The "Delete variant" call is used to delete a specific variant. + + :param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of + the variant that you want to delete + :type variant_uid: str + :return: the result of the `client.delete()` method, which is likely a response object or a + boolean value indicating the success of the deletion operation. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants + >>> result = client.stack('api_key').variant_group('variant_group_uid').variants('variant_uid').delete().json() + >>> # For ungrouped variants + >>> result = client.stack('api_key').variants('variant_uid').delete().json() + + ------------------------------- + """ + if variant_uid is not None and variant_uid != '': + self.variant_uid = variant_uid + if self.variant_group_uid: + self.validate_variant_group_uid() + self.validate_variant_uid() + url = f"{self.path}/{self.variant_uid}" + return self.client.delete(url, headers = self.client.headers, params = self.params) + + def fetchByUIDs(self, variant_uids: list): + """ + The fetchByUIDs on variant will allow to fetch specific variants by their UIDs. + + :param variant_uids: The `variant_uids` parameter is a list of strings that represents the unique identifiers of + the variants that you want to fetch + :type variant_uids: list + :return: Json, with variant details for the specified UIDs. + ------------------------------- + [Example:] + + >>> import contentstack_management + >>> client = contentstack_management.Client(authtoken='your_authtoken') + >>> # For grouped variants + >>> result = client.stack('api_key').variant_group('variant_group_uid').variants().fetchByUIDs(['uid1', 'uid2']).json() + >>> # For ungrouped variants + >>> result = client.stack('api_key').variants().fetchByUIDs(['uid1', 'uid2']).json() + + ------------------------------- + """ + if not isinstance(variant_uids, list) or len(variant_uids) == 0: + raise ArgumentException("variant_uids must be a non-empty list") + + if self.variant_group_uid: + self.validate_variant_group_uid() + + # Convert list to comma-separated string + uids_param = ','.join(variant_uids) + params = self.params.copy() + params['uid'] = uids_param + + return self.client.get(self.path, headers = self.client.headers, params = params) + + def validate_variant_group_uid(self): + """ + The function checks if the variant_group_uid is None or an empty string and raises an ArgumentException + if it is. + """ + + if self.variant_group_uid is None or self.variant_group_uid == '': + raise ArgumentException("variant group Uid is required") + + def validate_variant_uid(self): + """ + The function checks if the variant_uid is None or an empty string and raises an ArgumentException + if it is. + """ + + if self.variant_uid is None or self.variant_uid == '': + raise ArgumentException("variant Uid is required") \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 6c883a7..de5768f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -7,6 +7,7 @@ # pytest --cov=contentstack # pytest -v --cov=contentstack --cov-report=html # pytest --html=tests/report/test-report.html +# Sanity: PYTHONPATH=. pytest tests/unit/ --html=tests/report/test-report.html from unittest import TestLoader, TestSuite from .api.aliases.test_alias import AliaseApiTests diff --git a/tests/cred.py b/tests/cred.py index 85e6a1a..3f28543 100644 --- a/tests/cred.py +++ b/tests/cred.py @@ -40,6 +40,8 @@ default_management_token_uid = "management_token_uid" #default management token uid default_publish_queue_uid = "publish_queue_uid" # default publish queue uid default_extension_uid = "extension_uid" # default publish queue uid +default_variant_group_uid = "variant_group_uid" # default variant group uid +default_variant_uid = "variant_uid" # default variant uid def get_credentials(): load_dotenv() @@ -85,6 +87,8 @@ def get_credentials(): "delivery_token_uid": os.getenv("DELIVERY_TOKEN_UID", default_delivery_token_uid), "management_token_uid": os.getenv("MANAGEMENT_TOKEN_UID", default_management_token_uid), "publish_queue_uid": os.getenv("PUBLISH_QUEUE_UID", default_publish_queue_uid), - "extension_uid": os.getenv("EXTENSION_UID", default_extension_uid) + "extension_uid": os.getenv("EXTENSION_UID", default_extension_uid), + "variant_group_uid": os.getenv("VARIANT_GROUP_UID", default_variant_group_uid), + "variant_uid": os.getenv("VARIANT_UID", default_variant_uid) } return credentials diff --git a/tests/unit/entry_variants/__init__.py b/tests/unit/entry_variants/__init__.py new file mode 100644 index 0000000..5c05611 --- /dev/null +++ b/tests/unit/entry_variants/__init__.py @@ -0,0 +1 @@ +# Unit tests for entry_variants module diff --git a/tests/unit/entry_variants/test_entry_variants.py b/tests/unit/entry_variants/test_entry_variants.py new file mode 100644 index 0000000..2eabed9 --- /dev/null +++ b/tests/unit/entry_variants/test_entry_variants.py @@ -0,0 +1,169 @@ +import unittest +import contentstack_management +from tests.cred import get_credentials + +credentials = get_credentials() +username = credentials["username"] +password = credentials["password"] +host = credentials["host"] +api_key = credentials["api_key"] +content_type_uid = credentials["content_type_uid"] +entry_uid = credentials["entry_uid"] +variant_uid = credentials["variant_uid"] + +class EntryVariantsUnitTests(unittest.TestCase): + + def setUp(self): + self.client = contentstack_management.Client(host=host) + self.client.login(username, password) + + def test_find_all_entry_variants(self): + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants().find() + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_find_entry_variants_with_params(self): + params = {"limit": 10, "skip": 0} + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants().find(params) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants?limit=10&skip=0") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_query_entry_variants(self): + query = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants() + query.add_param("title", "variant title") + response = query.find() + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants?title=variant+title") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_create_entry_variant(self): + data = { + "customized_fields": [ + "title", + "url" + ], + "base_entry_version": 10, + "entry": { + "title": "example", + "url": "/example" + } + } + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants().create(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants") + self.assertEqual(response.request.method, "POST") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_fetch_entry_variant(self): + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants(variant_uid).fetch() + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_entry_variant_with_params(self): + params = {"include_count": True} + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants(variant_uid).fetch(params=params) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}?include_count=True") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_update_entry_variant(self): + data = { + "customized_fields": [ + "title", + "url", + "description" + ], + "entry": { + "title": "updated example", + "url": "/updated-example", + "description": "Updated description" + } + } + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants(variant_uid).update(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}") + self.assertEqual(response.request.method, "PUT") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_delete_entry_variant(self): + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants(variant_uid).delete() + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}") + self.assertEqual(response.request.method, "DELETE") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_versions_entry_variant(self): + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants(variant_uid).versions() + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}/versions") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_versions_entry_variant_with_params(self): + params = {"limit": 10, "skip": 0} + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants(variant_uid).versions(params=params) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}/variants/{variant_uid}/versions?limit=10&skip=0") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_include_variants(self): + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).includeVariants('true', variant_uid) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}?include_variants=true&variant_uid={variant_uid}") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_include_variants_with_params(self): + params = {"limit": 10, "skip": 0} + response = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).includeVariants('true', variant_uid, params) + self.assertEqual(response.request.url, f"{self.client.endpoint}content_types/{content_type_uid}/entries/{entry_uid}?limit=10&skip=0&include_variants=true&variant_uid={variant_uid}") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_validate_content_type_uid_with_valid_uid(self): + entry_variants = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants() + # This should not raise an exception + try: + entry_variants.validate_content_type_uid() + except Exception as e: + self.fail(f"validate_content_type_uid() raised {type(e).__name__} unexpectedly!") + + def test_validate_content_type_uid_with_invalid_uid(self): + entry_variants = self.client.stack(api_key).content_types("").entry(entry_uid).variants() + with self.assertRaises(Exception): + entry_variants.validate_content_type_uid() + + def test_validate_content_type_uid_with_none_uid(self): + # Create entry_variants instance directly to test validation + entry_variants = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants() + entry_variants.content_type_uid = None + with self.assertRaises(Exception): + entry_variants.validate_content_type_uid() + + def test_validate_entry_uid_with_valid_uid(self): + entry_variants = self.client.stack(api_key).content_types(content_type_uid).entry(entry_uid).variants() + # This should not raise an exception + try: + entry_variants.validate_entry_uid() + except Exception as e: + self.fail(f"validate_entry_uid() raised {type(e).__name__} unexpectedly!") + + def test_validate_entry_uid_with_invalid_uid(self): + entry_variants = self.client.stack(api_key).content_types(content_type_uid).entry("").variants() + with self.assertRaises(Exception): + entry_variants.validate_entry_uid() + + def test_validate_entry_uid_with_none_uid(self): + entry_variants = self.client.stack(api_key).content_types(content_type_uid).entry(None).variants() + with self.assertRaises(Exception): + entry_variants.validate_entry_uid() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/variant_group/__init__.py b/tests/unit/variant_group/__init__.py new file mode 100644 index 0000000..9e54a9e --- /dev/null +++ b/tests/unit/variant_group/__init__.py @@ -0,0 +1 @@ +# Unit tests for variant_group module diff --git a/tests/unit/variant_group/test_variant_group.py b/tests/unit/variant_group/test_variant_group.py new file mode 100644 index 0000000..fd68cd0 --- /dev/null +++ b/tests/unit/variant_group/test_variant_group.py @@ -0,0 +1,115 @@ +import unittest +import contentstack_management +from tests.cred import get_credentials + +credentials = get_credentials() +username = credentials["username"] +password = credentials["password"] +host = credentials["host"] +api_key = credentials["api_key"] +variant_group_uid = credentials["variant_group_uid"] + +class VariantGroupUnitTests(unittest.TestCase): + + def setUp(self): + self.client = contentstack_management.Client(host=host) + self.client.login(username, password) + + def test_find_all_variant_groups(self): + response = self.client.stack(api_key).variant_group().find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_find_variant_groups_with_params(self): + query = self.client.stack(api_key).variant_group() + query.add_param("limit", 10) + query.add_param("skip", 0) + response = query.find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups?limit=10&skip=0") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_query_variant_groups(self): + query_params = {"name": "Colors"} + response = self.client.stack(api_key).variant_group().query(query_params).find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups?name=Colors") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_variant_group(self): + response = self.client.stack(api_key).variant_group(variant_group_uid).fetch() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_variant_group_with_params(self): + query = self.client.stack(api_key).variant_group(variant_group_uid) + query.add_param("include_count", True) + response = query.fetch() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}?include_count=True") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_create_variant_group(self): + data = { + "variant_group": { + "name": "Colors", + "content_types": [ + "iphone_product_page" + ], + "description": "Color variants for product pages" + } + } + response = self.client.stack(api_key).variant_group().create(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups") + self.assertEqual(response.request.method, "POST") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_update_variant_group(self): + data = { + "variant_group": { + "name": "Updated Colors", + "content_types": [ + "iphone_product_page", + "android_product_page" + ], + "description": "Updated color variants for product pages" + } + } + response = self.client.stack(api_key).variant_group(variant_group_uid).update(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}") + self.assertEqual(response.request.method, "PUT") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_delete_variant_group(self): + response = self.client.stack(api_key).variant_group(variant_group_uid).delete() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}") + self.assertEqual(response.request.method, "DELETE") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_validate_uid_with_valid_uid(self): + variant_group = self.client.stack(api_key).variant_group(variant_group_uid) + # This should not raise an exception + try: + variant_group.validate_uid() + except Exception as e: + self.fail(f"validate_uid() raised {type(e).__name__} unexpectedly!") + + def test_validate_uid_with_invalid_uid(self): + variant_group = self.client.stack(api_key).variant_group("") + with self.assertRaises(Exception): + variant_group.validate_uid() + + def test_validate_uid_with_none_uid(self): + variant_group = self.client.stack(api_key).variant_group(None) + with self.assertRaises(Exception): + variant_group.validate_uid() + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/variants/__init__.py b/tests/unit/variants/__init__.py new file mode 100644 index 0000000..d4e0366 --- /dev/null +++ b/tests/unit/variants/__init__.py @@ -0,0 +1 @@ +# Unit tests for variants module diff --git a/tests/unit/variants/test_variants.py b/tests/unit/variants/test_variants.py new file mode 100644 index 0000000..c0af998 --- /dev/null +++ b/tests/unit/variants/test_variants.py @@ -0,0 +1,178 @@ +import unittest +import contentstack_management +from tests.cred import get_credentials + +credentials = get_credentials() +username = credentials["username"] +password = credentials["password"] +host = credentials["host"] +api_key = credentials["api_key"] +variant_group_uid = credentials["variant_group_uid"] +variant_uid = credentials["variant_uid"] + +class VariantsUnitTests(unittest.TestCase): + + def setUp(self): + self.client = contentstack_management.Client(host=host) + self.client.login(username, password) + + def test_find_all_variants(self): + response = self.client.stack(api_key).variants().find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variants") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_find_variants_with_params(self): + params = {"limit": 10, "skip": 0} + response = self.client.stack(api_key).variants().find(params) + self.assertEqual(response.request.url, f"{self.client.endpoint}variants?limit=10&skip=0") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_find_grouped_variants(self): + response = self.client.stack(api_key).variant_group(variant_group_uid).variants().find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_find_grouped_variants_with_params(self): + params = {"limit": 10, "skip": 0} + response = self.client.stack(api_key).variant_group(variant_group_uid).variants().find(params) + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants?limit=10&skip=0") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_query_variants(self): + query_params = {"title": "variant title"} + response = self.client.stack(api_key).variants().query(query_params).find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variants?title=variant+title") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_query_grouped_variants(self): + query_params = {"title": "variant title"} + response = self.client.stack(api_key).variant_group(variant_group_uid).variants().query(query_params).find() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants?title=variant+title") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_variant(self): + response = self.client.stack(api_key).variants(variant_uid).fetch() + self.assertEqual(response.request.url, f"{self.client.endpoint}variants/{variant_uid}") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_variant_with_params(self): + params = {"include_count": True} + response = self.client.stack(api_key).variants(variant_uid).fetch(params=params) + self.assertEqual(response.request.url, f"{self.client.endpoint}variants/{variant_uid}?include_count=True") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_grouped_variant(self): + response = self.client.stack(api_key).variant_group(variant_group_uid).variants(variant_uid).fetch() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants/{variant_uid}") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_fetch_grouped_variant_with_params(self): + params = {"include_count": True} + response = self.client.stack(api_key).variant_group(variant_group_uid).variants(variant_uid).fetch(params=params) + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants/{variant_uid}?include_count=True") + self.assertEqual(response.request.method, "GET") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + self.assertEqual(response.request.body, None) + + def test_create_variant(self): + data = { + "variant": { + "title": "Red Variant", + "description": "Red color variant for products", + "is_default": False + } + } + response = self.client.stack(api_key).variants().create(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}variants") + self.assertEqual(response.request.method, "POST") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_create_grouped_variant(self): + data = { + "variant": { + "title": "Red Variant", + "description": "Red color variant for products", + "is_default": False + } + } + response = self.client.stack(api_key).variant_group(variant_group_uid).variants().create(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants") + self.assertEqual(response.request.method, "POST") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_update_variant(self): + data = { + "variant": { + "title": "Updated Red Variant", + "description": "Updated red color variant for products", + "is_default": True + } + } + response = self.client.stack(api_key).variants(variant_uid).update(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}variants/{variant_uid}") + self.assertEqual(response.request.method, "PUT") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_update_grouped_variant(self): + data = { + "variant": { + "title": "Updated Red Variant", + "description": "Updated red color variant for products", + "is_default": True + } + } + response = self.client.stack(api_key).variant_group(variant_group_uid).variants(variant_uid).update(data) + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants/{variant_uid}") + self.assertEqual(response.request.method, "PUT") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_delete_variant(self): + response = self.client.stack(api_key).variants(variant_uid).delete() + self.assertEqual(response.request.url, f"{self.client.endpoint}variants/{variant_uid}") + self.assertEqual(response.request.method, "DELETE") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_delete_grouped_variant(self): + response = self.client.stack(api_key).variant_group(variant_group_uid).variants(variant_uid).delete() + self.assertEqual(response.request.url, f"{self.client.endpoint}variant_groups/{variant_group_uid}/variants/{variant_uid}") + self.assertEqual(response.request.method, "DELETE") + self.assertEqual(response.request.headers["Content-Type"], "application/json") + + def test_validate_variant_group_uid_with_valid_uid(self): + variants = self.client.stack(api_key).variant_group(variant_group_uid).variants() + # This should not raise an exception + try: + variants.validate_variant_group_uid() + except Exception as e: + self.fail(f"validate_variant_group_uid() raised {type(e).__name__} unexpectedly!") + + def test_validate_variant_group_uid_with_invalid_uid(self): + variants = self.client.stack(api_key).variant_group("").variants() + with self.assertRaises(Exception): + variants.validate_variant_group_uid() + + def test_validate_variant_group_uid_with_none_uid(self): + variants = self.client.stack(api_key).variant_group(None).variants() + with self.assertRaises(Exception): + variants.validate_variant_group_uid() + +if __name__ == '__main__': + unittest.main() From 5df3094c9aba33ca41869889198a82debf0dc54a Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Mon, 18 Aug 2025 12:47:18 +0530 Subject: [PATCH 2/2] updated talisman file --- .talismanrc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.talismanrc b/.talismanrc index 5a277ec..b5272a4 100644 --- a/.talismanrc +++ b/.talismanrc @@ -369,3 +369,19 @@ fileignoreconfig: - filename: tests/unit/global_fields/test_global_fields_unittest.py checksum: 46297a6fbf321dfe4b85a3d2c1089614af8c9590d8352238c800ad69955b4b6a version: "1.0" +fileignoreconfig: +- filename: tests/unit/variant_group/test_variant_group.py + checksum: 7c3d5ee03ed59891c20f2447c9d6da423b28855455639f6a1f299a03c1ebe744 +- filename: tests/unit/entry_variants/test_entry_variants.py + checksum: 4f0d963c7e153974486af64e642dcfbf2d967448abe2344bcc3bddfb898384dc +- filename: contentstack_management/variants/variants.py + checksum: c06b3471717db66b9e558c4c735a7690098839d87d96ef4b4be4c573b4a54709 +- filename: contentstack_management/entries/entry.py + checksum: 141933cd737709129bfe891cbbc5d19ed1435ac9611cc5fdf420087b98774ce9 +- filename: contentstack_management/entry_variants/entry_variants.py + checksum: a084c1b235d464def36854e662071c744b2f71abe33038ade439bbe313deea5d +- filename: contentstack_management/variant_group/variant_group.py + checksum: a1d4824626aea510cd80c9b1920ede40cdc374ee9a81169811ca7980a9b67b32 +- filename: tests/unit/variants/test_variants.py + checksum: 866f8b27d41694d0b2a7273c842b2b2b25cab6dac5919edb7d4d658bc0aa20cb +version: "1.0"