Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# CHANGELOG

## _v2.4.1_

### **Date: 10-November-2025**

- Improved Error messages.

## _v2.4.0_

### **Date: 01-September-2025**
Expand Down
2 changes: 1 addition & 1 deletion contentstack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
__title__ = 'contentstack-delivery-python'
__author__ = 'contentstack'
__status__ = 'debug'
__version__ = 'v2.4.0'
__version__ = 'v2.4.1'
__endpoint__ = 'cdn.contentstack.io'
__email__ = '[email protected]'
__developer_email__ = '[email protected]'
Expand Down
5 changes: 3 additions & 2 deletions contentstack/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import logging
from urllib import parse
from contentstack.error_messages import ErrorMessages

class Asset:
r"""`Asset` refer to all the media files (images, videos, PDFs, audio files, and so on)."""
Expand All @@ -15,7 +16,7 @@ def __init__(self, http_instance, uid=None, logger=None):
self.asset_params = {}
self.__uid = uid
if self.__uid is None or self.__uid.strip() == 0:
raise KeyError('Please provide valid uid')
raise KeyError(ErrorMessages.INVALID_UID)
self.base_url = f'{self.http_instance.endpoint}/assets/{self.__uid}'
if 'environment' in self.http_instance.headers:
self.asset_params['environment'] = self.http_instance.headers['environment']
Expand Down Expand Up @@ -68,7 +69,7 @@ def params(self, key, value):
-----------------------------
"""
if None in (key, value) or not isinstance(key, str):
raise KeyError('Kindly provide valid params')
raise KeyError(ErrorMessages.INVALID_PARAMS)
self.asset_params[key] = value
return self

Expand Down
15 changes: 10 additions & 5 deletions contentstack/basequery.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import enum
import logging
from contentstack.error_messages import ErrorMessages

class QueryOperation(enum.Enum):
"""
Expand Down Expand Up @@ -137,8 +138,10 @@ def param(self, key: str, value):

-----------------------------------
"""
if None in (key, value):
raise KeyError('Invalid key or value')
if key is None:
raise KeyError(ErrorMessages.INVALID_KEY)
if value is None:
raise KeyError(ErrorMessages.INVALID_VALUE)
self.query_params[key] = str(value)
return self

Expand All @@ -164,8 +167,10 @@ def query(self, key: str, value):
Returns:
self-- Class instance, So that method chaining can be performed
"""
if None in (key, value):
raise KeyError('Invalid key or value')
if key is None:
raise KeyError(ErrorMessages.INVALID_KEY)
if value is None:
raise KeyError(ErrorMessages.INVALID_VALUE)
self.parameters[key] = str(value)
return self

Expand All @@ -186,7 +191,7 @@ def remove_param(self, key: str):
----------------------------------
"""
if key is None:
raise ValueError('Kindly provide valid key')
raise ValueError(ErrorMessages.INVALID_KEY)
if key in self.query_params:
self.query_params.pop(key, None)
return self
12 changes: 5 additions & 7 deletions contentstack/contenttype.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import json
import logging
from urllib import parse
from contentstack.error_messages import ErrorMessages

from contentstack.entry import Entry
from contentstack.query import Query
Expand Down Expand Up @@ -46,11 +47,9 @@ def entry(self, entry_uid: str):
--------------------------------
"""
if self.__content_type_uid is None:
raise PermissionError('Please provide valid content_type_uid')
raise PermissionError(ErrorMessages.INVALID_CONTENT_TYPE_UID)
if entry_uid is None:
raise PermissionError(json.dumps({
"message": 'Please provide valid entry uid',
"message_detail": 'Entry UID can not be None'}))
raise PermissionError(ErrorMessages.INVALID_UID)
entry = Entry(self.http_instance,
self.__content_type_uid, entry_uid=entry_uid)
return entry
Expand All @@ -69,7 +68,7 @@ def query(self):
------------------------------
"""
if self.__content_type_uid is None:
raise PermissionError('Kindly provide content_type_uid')
raise PermissionError(ErrorMessages.CONTENT_TYPE_UID_REQUIRED)
return Query(self.http_instance, self.__content_type_uid)

def fetch(self):
Expand All @@ -87,8 +86,7 @@ def fetch(self):
------------------------------
"""
if self.__content_type_uid is None:
raise KeyError(
'content_type_uid can not be None to fetch contenttype')
raise KeyError(ErrorMessages.CONTENT_TYPE_UID_REQUIRED)
self.local_param['environment'] = self.http_instance.headers['environment']
uri = f'{self.http_instance.endpoint}/content_types/{self.__content_type_uid}'
encoded_params = parse.urlencode(self.local_param)
Expand Down
5 changes: 3 additions & 2 deletions contentstack/controller.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import requests
from requests.utils import guess_json_utf
from contentstack.error_messages import ErrorMessages


class RequestError(Exception):
Expand All @@ -18,14 +19,14 @@ def get_request(session, url, headers, timeout):
response.encoding = 'utf-8'
except requests.exceptions.RequestException as e:
error = {
'error': f"Failed to connect to {url}: {str(e)}",
'error': ErrorMessages.CONNECTION_FAILED.format(url=url, error=str(e)),
'error_code': '400',
'error_message': {str(e)}
}
raise RequestError(error)
except Exception as e:
error = {
'error': f"An unexpected error while making request to {url}, '400', {str(e)}",
'error': ErrorMessages.OPERATION_FAILED.format(url=url, error=str(e)),
'error_code': '400',
'error_message': {str(e)}
}
Expand Down
4 changes: 3 additions & 1 deletion contentstack/deep_merge_lp.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from contentstack.error_messages import ErrorMessages

class DeepMergeMixin:

def __init__(self, entry_response, lp_response):
if not isinstance(entry_response, list) or not isinstance(lp_response, list):
raise TypeError("Both entry_response and lp_response must be lists of dictionaries")
raise TypeError(ErrorMessages.INVALID_RESPONSE_TYPE)

self.entry_response = entry_response
self.lp_response = lp_response
Expand Down
18 changes: 9 additions & 9 deletions contentstack/entry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#min-similarity-lines=10
import logging
from urllib import parse
from contentstack.error_messages import ErrorMessages

from contentstack.deep_merge_lp import DeepMergeMixin
from contentstack.entryqueryable import EntryQueryable
Expand Down Expand Up @@ -49,7 +50,7 @@ def environment(self, environment):
------------------------------
"""
if environment is None:
raise KeyError('Kindly provide a valid environment')
raise KeyError(ErrorMessages.INVALID_ENVIRONMENT)
self.http_instance.headers['environment'] = environment
return self

Expand All @@ -72,7 +73,7 @@ def version(self, version):
------------------------------
"""
if version is None:
raise KeyError('Kindly provide a valid version')
raise KeyError(ErrorMessages.INVALID_VERSION)
self.entry_param['version'] = version
return self

Expand All @@ -94,7 +95,7 @@ def param(self, key, value):
-----------------------------
"""
if None in (key, value) and not isinstance(key, str):
raise ValueError('Kindly provide valid key and value arguments')
raise ValueError(ErrorMessages.INVALID_KEY_VALUE_ARGS)
self.entry_param[key] = value
return self

Expand All @@ -113,7 +114,7 @@ def include_fallback(self):
>>> result = entry.fetch()
----------------------------
"""
print('Requesting fallback....')
print(ErrorMessages.REQUESTING_FALLBACK)
self.entry_param['include_fallback'] = 'true'
return self

Expand Down Expand Up @@ -157,8 +158,7 @@ def __get_base_url(self, endpoint=''):
if endpoint is not None and endpoint.strip(): # .strip() removes leading/trailing whitespace
self.http_instance.endpoint = endpoint
if None in (self.http_instance, self.content_type_id, self.entry_uid):
raise KeyError(
'Provide valid http_instance, content_type_uid or entry_uid')
raise KeyError(ErrorMessages.INVALID_KEY_OR_VALUE)
url = f'{self.http_instance.endpoint}/content_types/{self.content_type_id}/entries/{self.entry_uid}'
return url

Expand Down Expand Up @@ -217,12 +217,12 @@ def _merged_response(self):
if not isinstance(lp_entry, list):
lp_entry = [lp_entry] # Wrap in a list if it's a dict
if not all(isinstance(item, dict) for item in entry_response):
raise TypeError(f"entry_response must be a list of dictionaries. Got: {entry_response}")
raise TypeError(ErrorMessages.INVALID_ENTRY_RESPONSE)
if not all(isinstance(item, dict) for item in lp_entry):
raise TypeError(f"lp_entry must be a list of dictionaries. Got: {lp_entry}")
raise TypeError(ErrorMessages.INVALID_LP_ENTRY)
merged_response = DeepMergeMixin(entry_response, lp_entry).to_dict() # Convert to dictionary
return merged_response # Now correctly returns a dictionary
raise ValueError("Missing required keys in live_preview data")
raise ValueError(ErrorMessages.MISSING_LIVE_PREVIEW_KEYS)

def variants(self, variant_uid: str | list[str], params: dict = None):
"""
Expand Down
5 changes: 3 additions & 2 deletions contentstack/entryqueryable.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
that is used as parents class for the query and entry classes
"""
import logging
from contentstack.error_messages import ErrorMessages

class EntryQueryable:
"""
Expand Down Expand Up @@ -55,7 +56,7 @@ def only(self, field_uid: str):
if isinstance(field_uid, str):
self.entry_queryable_param['only[BASE][]'] = field_uid
else:
raise KeyError("Invalid field_uid provided")
raise KeyError(ErrorMessages.INVALID_FIELD_UID)
return self

def excepts(self, field_uid: str):
Expand All @@ -69,7 +70,7 @@ def excepts(self, field_uid: str):
if isinstance(field_uid, str):
self.entry_queryable_param['except[BASE][]'] = field_uid
else:
raise KeyError("Invalid field_uid provided")
raise KeyError(ErrorMessages.INVALID_FIELD_UID)
return self

def include_reference(self, field_uid):
Expand Down
59 changes: 59 additions & 0 deletions contentstack/error_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Centralized error messages for the Contentstack Python SDK.
All error messages should be defined here and imported where needed.
"""

class ErrorMessages:
# BaseQuery errors
INVALID_KEY = "Invalid key. Provide a valid key and try again."
INVALID_VALUE = "Invalid value. Provide a valid value and try again."
INVALID_KEY_OR_VALUE = "Invalid key or value. Provide valid values and try again."

# ContentType errors
INVALID_CONTENT_TYPE_UID = "Content type UID is invalid. Provide a valid UID and try again."
CONTENT_TYPE_UID_REQUIRED = "Content type UID is required. Provide a UID and try again."

# EntryQueryable errors
INVALID_FIELD_UID = "Invalid field UID. Provide a valid UID and try again."

# DeepMergeLp errors
INVALID_RESPONSE_TYPE = "Invalid input. entry_response and lp_response must be lists of dictionaries. Update the values and try again."

# Entry errors
INVALID_ENVIRONMENT = "Invalid environment. Provide a valid environment and try again."
INVALID_VERSION = "Invalid version. Provide a valid version and try again."
INVALID_KEY_VALUE_ARGS = "Invalid key or value arguments. Provide valid values and try again."
REQUESTING_FALLBACK = "Requesting fallback content for the specified locale."
INVALID_ENTRY_RESPONSE = "Invalid entry_response format. Provide a list of dictionaries, each containing entry data, and try again."
INVALID_LP_ENTRY = "Invalid lp_entry. Provide a list of dictionaries and try again."
MISSING_LIVE_PREVIEW_KEYS = "Missing required keys in live preview data. Provide all required keys and try again."

# Asset errors
INVALID_UID = "Invalid UID. Provide a valid UID and try again."
INVALID_PARAMS = "Invalid parameters. Provide valid parameters and try again."

# Controller errors
CONNECTION_FAILED = "Connection failed. Unable to connect to {url}. Error: {error}. Check your connection and try again."
OPERATION_FAILED = "Operation failed. An unexpected error occurred while making request to {url}. Error: {error}. Check your inputs and try again."

# Query errors
DEPRECATED_SEARCH = """The search() method is deprecated since version 1.7.0. Use regex() instead.
Example: query.regex("title", "^Blog.*") to search for titles starting with "Blog"."""
INVALID_JSON = "Invalid JSON. Error: {error}. Provide valid JSON and try again."
MISSING_ENTRIES_KEY = "Invalid response. The 'entries' key is missing. Include the 'entries' key and try again."
MISSING_ENTRY_KEY = "Invalid lp_response. The 'entry' key is missing. Include the 'entry' key and try again."

# Variants errors
ENTRY_UID_REQUIRED = "Missing entry UID. Provide a valid UID and try again."

# Stack errors
INVALID_STACK_UID = "Invalid UID. Provide a valid UID and try again."

# Utility errors
INVALID_PARAMS_TYPE = "Invalid params. Provide a dictionary and try again."
INVALID_URL_PARAMS = "Invalid input. Provide base_url as a string and params as a dictionary, then try again."

# Stack errors
INVALID_API_KEY = "Invalid API key. Provide a valid API key and try again."
INVALID_DELIVERY_TOKEN = "Invalid delivery token. Provide a valid delivery token and try again."
INVALID_ENVIRONMENT_TOKEN = "Invalid environment. Provide a valid environment and try again."
Loading
Loading