Skip to content

Commit 8ad5d93

Browse files
authored
Merge pull request #121 from contentstack/staging
DX | 06-05-2025 | Release
2 parents 51e1ba8 + 30fc4b3 commit 8ad5d93

14 files changed

+107
-79
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# CHANGELOG
22

3+
## _v2.0.0_
4+
5+
### **Date: 28-APRIL-2025**
6+
7+
- Custom logger support
8+
39
## _v1.11.2_
410

511
### **Date: 21-APRIL-2025**

contentstack/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
__title__ = 'contentstack-delivery-python'
2323
__author__ = 'contentstack'
2424
__status__ = 'debug'
25-
__version__ = 'v1.11.2'
25+
__version__ = 'v2.0.0'
2626
__endpoint__ = 'cdn.contentstack.io'
2727
__email__ = '[email protected]'
2828
__developer_email__ = '[email protected]'

contentstack/asset.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,10 @@
77
import logging
88
from urllib import parse
99

10-
log = logging.getLogger(__name__)
11-
12-
1310
class Asset:
1411
r"""`Asset` refer to all the media files (images, videos, PDFs, audio files, and so on)."""
1512

16-
def __init__(self, http_instance, uid=None):
13+
def __init__(self, http_instance, uid=None, logger=None):
1714
self.http_instance = http_instance
1815
self.asset_params = {}
1916
self.__uid = uid
@@ -22,6 +19,7 @@ def __init__(self, http_instance, uid=None):
2219
self.base_url = f'{self.http_instance.endpoint}/assets/{self.__uid}'
2320
if 'environment' in self.http_instance.headers:
2421
self.asset_params['environment'] = self.http_instance.headers['environment']
22+
self.logger = logger or logging.getLogger(__name__)
2523

2624
def environment(self, environment):
2725
r"""Provide the name of the environment if you wish to retrieve the assets published

contentstack/assetquery.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,20 @@
99
from contentstack.basequery import BaseQuery
1010
from contentstack.utility import Utils
1111

12-
log = logging.getLogger(__name__)
13-
14-
1512
class AssetQuery(BaseQuery):
1613
"""
1714
This call fetches the list of all the assets of a particular stack.
1815
"""
1916

20-
def __init__(self, http_instance):
17+
def __init__(self, http_instance, logger=None):
2118
super().__init__()
2219
self.http_instance = http_instance
2320
self.asset_query_params = {}
2421
self.base_url = f"{self.http_instance.endpoint}/assets"
2522
if "environment" in self.http_instance.headers:
2623
env = self.http_instance.headers["environment"]
2724
self.base_url = f"{self.base_url}?environment={env}"
25+
self.logger = logger or logging.getLogger(__name__)
2826

2927
def environment(self, environment):
3028
r"""Provide the name of the environment if you wish to retrieve the assets published

contentstack/basequery.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import enum
22
import logging
33

4-
log = logging.getLogger(__name__)
5-
6-
74
class QueryOperation(enum.Enum):
85
"""
96
QueryOperation is enum that Provides Options to perform operation to query the result.
@@ -38,9 +35,10 @@ class BaseQuery:
3835
Common Query class works for Query As well as Asset
3936
"""
4037

41-
def __init__(self):
38+
def __init__(self, logger=None):
4239
self.parameters = {}
4340
self.query_params = {}
41+
self.logger = logger or logging.getLogger(__name__)
4442

4543
def where(self, field_uid: str, query_operation: QueryOperation, fields=None):
4644
"""

contentstack/contenttype.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@
1414
from contentstack.entry import Entry
1515
from contentstack.query import Query
1616

17-
log = logging.getLogger(__name__)
18-
19-
2017
class ContentType:
2118
"""
2219
Content type defines the structure or schema of a page or a
@@ -26,10 +23,11 @@ class ContentType:
2623
content type.
2724
"""
2825

29-
def __init__(self, http_instance, content_type_uid):
26+
def __init__(self, http_instance, content_type_uid, logger=None):
3027
self.http_instance = http_instance
3128
self.__content_type_uid = content_type_uid
3229
self.local_param = {}
30+
self.logger = logger or logging.getLogger(__name__)
3331

3432
def entry(self, entry_uid: str):
3533
r"""

contentstack/entry.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
from contentstack.deep_merge_lp import DeepMergeMixin
1010
from contentstack.entryqueryable import EntryQueryable
1111

12-
log = logging.getLogger(__name__)
13-
14-
1512
class Entry(EntryQueryable):
1613
"""
1714
An entry is the actual piece of content that you want to publish.
@@ -23,14 +20,15 @@ class Entry(EntryQueryable):
2320
locale={locale_code}
2421
"""
2522

26-
def __init__(self, http_instance, content_type_uid, entry_uid):
23+
def __init__(self, http_instance, content_type_uid, entry_uid, logger=None):
2724
super().__init__()
2825
EntryQueryable.__init__(self)
2926
self.entry_param = {}
3027
self.http_instance = http_instance
3128
self.content_type_id = content_type_uid
3229
self.entry_uid = entry_uid
3330
self.base_url = self.__get_base_url()
31+
self.logger = logger or logging.getLogger(__name__)
3432

3533
def environment(self, environment):
3634
"""

contentstack/entryqueryable.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@
44
"""
55
import logging
66

7-
log = logging.getLogger(__name__)
8-
9-
107
class EntryQueryable:
118
"""
129
This class is base class for the Entry and Query class that shares common functions
1310
"""
1411

15-
def __init__(self):
12+
def __init__(self, logger=None):
1613
self.entry_queryable_param = {}
14+
self.logger = logger or logging.getLogger(__name__)
1715

1816
def locale(self, locale: str):
1917
"""

contentstack/https_connection.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@
99
import contentstack
1010
from contentstack.controller import get_request
1111

12-
log = logging.getLogger(__name__)
13-
14-
1512
def __get_os_platform():
1613
os_platform = platform.system()
1714
if os_platform == 'Darwin':

contentstack/image_transform.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,14 @@
88

99
import logging
1010

11-
log = logging.getLogger(__name__)
12-
1311

1412
class ImageTransform: # pylint: disable=too-few-public-methods
1513
"""
1614
The Image Delivery API is used to retrieve, manipulate and/or convert image
1715
files
1816
"""
1917

20-
def __init__(self, http_instance, image_url, **kwargs):
18+
def __init__(self, http_instance, image_url, logger=None, **kwargs):
2119
"""
2220
creates instance of the ImageTransform class
2321
:param httpInstance: instance of HttpsConnection
@@ -35,6 +33,7 @@ def __init__(self, http_instance, image_url, **kwargs):
3533
self.http_instance = http_instance
3634
self.image_url = image_url
3735
self.image_params = kwargs
36+
self.logger = logger or logging.getLogger(__name__)
3837

3938
def get_url(self):
4039
"""

contentstack/query.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
from contentstack.deep_merge_lp import DeepMergeMixin
1313
from contentstack.entryqueryable import EntryQueryable
1414

15-
log = logging.getLogger(__name__)
16-
1715

1816
class QueryType(enum.Enum):
1917
"""
@@ -40,7 +38,7 @@ class Query(BaseQuery, EntryQueryable):
4038
>>> result = query.locale('locale-code').excepts('field_uid').limit(4).skip(5).find()
4139
"""
4240

43-
def __init__(self, http_instance, content_type_uid):
41+
def __init__(self, http_instance, content_type_uid, logger=None):
4442
super().__init__()
4543
EntryQueryable.__init__(self)
4644
self.content_type_uid = content_type_uid
@@ -50,6 +48,7 @@ def __init__(self, http_instance, content_type_uid):
5048
'You are not allowed here without content_type_uid')
5149
self.base_url = f'{self.http_instance.endpoint}/content_types/{self.content_type_uid}/entries'
5250
self.base_url = self.__get_base_url()
51+
self.logger = logger or logging.getLogger(__name__)
5352

5453
def __get_base_url(self, endpoint=''):
5554
if endpoint is not None and endpoint.strip(): # .strip() removes leading/trailing whitespace

contentstack/stack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from contentstack.https_connection import HTTPSConnection
1010
from contentstack.image_transform import ImageTransform
1111

12-
log = logging.getLogger(__name__)
1312
DEFAULT_HOST = 'cdn.contentstack.io'
1413

1514

@@ -42,6 +41,7 @@ def __init__(self, api_key: str, delivery_token: str, environment: str,
4241
live_preview=None,
4342
branch=None,
4443
early_access = None,
44+
logger=None,
4545
):
4646
"""
4747
# Class that wraps the credentials of the authenticated user. Think of
@@ -78,7 +78,7 @@ def __init__(self, api_key: str, delivery_token: str, environment: str,
7878
live_preview={enable=True, authorization='your auth token'}, retry_strategy= _strategy)
7979
```
8080
"""
81-
logging.basicConfig(level=logging.DEBUG)
81+
self.logger = logger or logging.getLogger(__name__)
8282
self.headers = {}
8383
self._query_params = {}
8484
self.sync_param = {}

contentstack/utility.py

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,93 @@
11
"""
2+
Utility functions for logging and URL manipulation.
23
Last modified by ishaileshmishra on 06/08/20.
34
Copyright 2019 Contentstack. All rights reserved.
45
"""
56

67
import json
78
import logging
8-
from urllib.parse import urlencode, urljoin
9+
from urllib.parse import urlencode
910

10-
log = logging.getLogger(__name__)
1111

12-
13-
def config_logging(logging_type: logging.WARNING):
12+
def setup_logging(logging_type=logging.INFO, filename='app.log'):
1413
"""
15-
This is to create logging config
16-
:param logging_type: Level of the logging
17-
:return: basicConfig instance
14+
Global one-time logging configuration.
15+
Should be called from your main application entry point.
1816
"""
1917
logging.basicConfig(
20-
filename='app.log',
18+
filename=filename,
2119
level=logging_type,
2220
format='[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s',
23-
datefmt='%H:%M:%S'
21+
datefmt='%Y-%m-%d %H:%M:%S'
2422
)
2523

2624

2725
class Utils:
28-
2926
@staticmethod
30-
def config_logging():
31-
""" Setting up logging """
32-
logging.basicConfig(
33-
filename='report_log.log',
34-
format='%(asctime)s - %(message)s',
35-
level=logging.INFO
36-
)
27+
def setup_logger(name="AppLogger", level=logging.INFO, filename='app.log'):
28+
"""
29+
Creates and configures a named logger with file and console output.
30+
Prevents duplicate handlers.
31+
"""
32+
logger = logging.getLogger(name)
33+
if not logger.handlers:
34+
logger.setLevel(level)
3735

38-
@staticmethod
39-
def setup_logger():
40-
"""setup logger for the application"""
41-
return logging.getLogger("Config")
36+
formatter = logging.Formatter(
37+
'[%(asctime)s] %(levelname)s - %(name)s - %(message)s',
38+
datefmt='%Y-%m-%d %H:%M:%S'
39+
)
40+
41+
file_handler = logging.FileHandler(filename)
42+
file_handler.setFormatter(formatter)
43+
logger.addHandler(file_handler)
44+
45+
console_handler = logging.StreamHandler()
46+
console_handler.setFormatter(formatter)
47+
logger.addHandler(console_handler)
48+
49+
return logger
4250

4351
@staticmethod
44-
def log(message):
45-
"""this generates log message"""
46-
logging.debug(message)
52+
def log(message, level=logging.DEBUG):
53+
"""
54+
Log a message with the specified level.
55+
Default is DEBUG.
56+
"""
57+
logger = logging.getLogger("AppLogger")
58+
logger.log(level, message)
4759

4860
@staticmethod
4961
def do_url_encode(params):
5062
"""
51-
To encode url with query parameters
52-
:param params:
53-
:return: encoded url
63+
Encode query parameters to URL-safe format.
64+
:param params: Dictionary of parameters
65+
:return: Encoded URL query string
5466
"""
55-
return parse.urlencode(params)
67+
if not isinstance(params, dict):
68+
raise ValueError("params must be a dictionary")
69+
return urlencode(params, doseq=True)
5670

5771
@staticmethod
58-
def get_complete_url(base_url: str, params: dict) -> str:
72+
def get_complete_url(base_url: str, params: dict, skip_encoding=False) -> str:
5973
"""
60-
Creates a complete URL using base_url and their respective parameters.
61-
:param base_url: The base URL to which parameters are appended.
62-
:param params: A dictionary of parameters to be included in the URL.
63-
:return: A complete URL with encoded parameters.
74+
Construct a full URL by combining base URL and encoded parameters.
75+
Handles JSON stringification for the `query` key.
76+
:param base_url: Base API URL
77+
:param params: Dictionary of query parameters
78+
:param skip_encoding: Set True to skip URL encoding
79+
:return: Complete URL
6480
"""
65-
# Ensure 'query' is properly serialized as a JSON string without extra quotes
66-
if 'query' in params:
81+
if not isinstance(base_url, str) or not isinstance(params, dict):
82+
raise ValueError("base_url must be a string and params must be a dictionary")
83+
84+
if 'query' in params and not skip_encoding:
6785
params["query"] = json.dumps(params["query"], separators=(',', ':'))
6886

69-
# Encode parameters
70-
query_string = urlencode(params, doseq=True)
71-
72-
# Join base_url and query_string
73-
if '?' in base_url:
74-
return f'{base_url}&{query_string}'
87+
if not skip_encoding:
88+
query_string = urlencode(params, doseq=True)
7589
else:
76-
return f'{base_url}?{query_string}'
90+
query_string = "&".join(f"{k}={v}" for k, v in params.items())
91+
92+
# Append with appropriate separator
93+
return f'{base_url}&{query_string}' if '?' in base_url else f'{base_url}?{query_string}'

0 commit comments

Comments
 (0)