Contentstack 2.1.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {contentstack-2.1.0 → contentstack-2.2.0}/Contentstack.egg-info/PKG-INFO +1 -1
  2. {contentstack-2.1.0 → contentstack-2.2.0}/Contentstack.egg-info/SOURCES.txt +1 -0
  3. {contentstack-2.1.0 → contentstack-2.2.0}/PKG-INFO +1 -1
  4. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/__init__.py +1 -1
  5. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/contenttype.py +16 -0
  6. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/entry.py +17 -0
  7. contentstack-2.2.0/contentstack/variants.py +93 -0
  8. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_entry.py +42 -40
  9. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_live_preview.py +3 -3
  10. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_stack.py +8 -5
  11. {contentstack-2.1.0 → contentstack-2.2.0}/Contentstack.egg-info/dependency_links.txt +0 -0
  12. {contentstack-2.1.0 → contentstack-2.2.0}/Contentstack.egg-info/not-zip-safe +0 -0
  13. {contentstack-2.1.0 → contentstack-2.2.0}/Contentstack.egg-info/requires.txt +0 -0
  14. {contentstack-2.1.0 → contentstack-2.2.0}/Contentstack.egg-info/top_level.txt +0 -0
  15. {contentstack-2.1.0 → contentstack-2.2.0}/LICENSE +0 -0
  16. {contentstack-2.1.0 → contentstack-2.2.0}/README.md +0 -0
  17. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/asset.py +0 -0
  18. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/assetquery.py +0 -0
  19. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/basequery.py +0 -0
  20. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/controller.py +0 -0
  21. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/deep_merge_lp.py +0 -0
  22. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/entryqueryable.py +0 -0
  23. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/globalfields.py +0 -0
  24. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/https_connection.py +0 -0
  25. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/image_transform.py +0 -0
  26. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/query.py +0 -0
  27. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/stack.py +0 -0
  28. {contentstack-2.1.0 → contentstack-2.2.0}/contentstack/utility.py +0 -0
  29. {contentstack-2.1.0 → contentstack-2.2.0}/setup.cfg +0 -0
  30. {contentstack-2.1.0 → contentstack-2.2.0}/setup.py +0 -0
  31. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_assets.py +0 -0
  32. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_early_fetch.py +0 -0
  33. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_early_find.py +0 -0
  34. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_global_fields.py +0 -0
  35. {contentstack-2.1.0 → contentstack-2.2.0}/tests/test_query.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Contentstack
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Contentstack is a headless CMS with an API-first approach.
5
5
  Home-page: https://github.com/contentstack/contentstack-python
6
6
  Author: Contentstack
@@ -22,6 +22,7 @@ contentstack/image_transform.py
22
22
  contentstack/query.py
23
23
  contentstack/stack.py
24
24
  contentstack/utility.py
25
+ contentstack/variants.py
25
26
  tests/test_assets.py
26
27
  tests/test_early_fetch.py
27
28
  tests/test_early_find.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Contentstack
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Contentstack is a headless CMS with an API-first approach.
5
5
  Home-page: https://github.com/contentstack/contentstack-python
6
6
  Author: Contentstack
@@ -22,7 +22,7 @@ __all__ = (
22
22
  __title__ = 'contentstack-delivery-python'
23
23
  __author__ = 'contentstack'
24
24
  __status__ = 'debug'
25
- __version__ = 'v2.1.0'
25
+ __version__ = 'v2.2.0'
26
26
  __endpoint__ = 'cdn.contentstack.io'
27
27
  __email__ = 'support@contentstack.com'
28
28
  __developer_email__ = 'mobile@contentstack.com'
@@ -13,6 +13,7 @@ from urllib import parse
13
13
 
14
14
  from contentstack.entry import Entry
15
15
  from contentstack.query import Query
16
+ from contentstack.variants import Variants
16
17
 
17
18
  class ContentType:
18
19
  """
@@ -118,3 +119,18 @@ class ContentType:
118
119
  url = f'{endpoint}/content_types?{encoded_params}'
119
120
  result = self.http_instance.get(url)
120
121
  return result
122
+
123
+ def variants(self, variant_uid: str | list[str], params: dict = None):
124
+ """
125
+ Fetches the variants of the content type
126
+ :param variant_uid: {str} -- variant_uid
127
+ :return: Entry, so you can chain this call.
128
+ """
129
+ return Variants(
130
+ http_instance=self.http_instance,
131
+ content_type_uid=self.__content_type_uid,
132
+ entry_uid=None,
133
+ variant_uid=variant_uid,
134
+ params=params,
135
+ logger=None
136
+ )
@@ -8,6 +8,7 @@ from urllib import parse
8
8
 
9
9
  from contentstack.deep_merge_lp import DeepMergeMixin
10
10
  from contentstack.entryqueryable import EntryQueryable
11
+ from contentstack.variants import Variants
11
12
 
12
13
  class Entry(EntryQueryable):
13
14
  """
@@ -222,6 +223,22 @@ class Entry(EntryQueryable):
222
223
  merged_response = DeepMergeMixin(entry_response, lp_entry).to_dict() # Convert to dictionary
223
224
  return merged_response # Now correctly returns a dictionary
224
225
  raise ValueError("Missing required keys in live_preview data")
226
+
227
+ def variants(self, variant_uid: str | list[str], params: dict = None):
228
+ """
229
+ Fetches the variants of the entry
230
+ :param variant_uid: {str} -- variant_uid
231
+ :return: Entry, so you can chain this call.
232
+ """
233
+ return Variants(
234
+ http_instance=self.http_instance,
235
+ content_type_uid=self.content_type_id,
236
+ entry_uid=self.entry_uid,
237
+ variant_uid=variant_uid,
238
+ params=params,
239
+ logger=self.logger
240
+ )
241
+
225
242
 
226
243
 
227
244
 
@@ -0,0 +1,93 @@
1
+ import logging
2
+ from urllib import parse
3
+
4
+ from contentstack.entryqueryable import EntryQueryable
5
+
6
+ class Variants(EntryQueryable):
7
+ """
8
+ An entry is the actual piece of content that you want to publish.
9
+ Entries can be created for one of the available content types.
10
+
11
+ Entry works with
12
+ version={version_number}
13
+ environment={environment_name}
14
+ locale={locale_code}
15
+ """
16
+
17
+ def __init__(self,
18
+ http_instance=None,
19
+ content_type_uid=None,
20
+ entry_uid=None,
21
+ variant_uid=None,
22
+ params=None,
23
+ logger=None):
24
+
25
+ super().__init__()
26
+ EntryQueryable.__init__(self)
27
+ self.entry_param = {}
28
+ self.http_instance = http_instance
29
+ self.content_type_id = content_type_uid
30
+ self.entry_uid = entry_uid
31
+ self.variant_uid = variant_uid
32
+ self.logger = logger or logging.getLogger(__name__)
33
+ self.entry_param = params or {}
34
+
35
+ def find(self, params=None):
36
+ """
37
+ find the variants of the entry of a particular content type
38
+ :param self.variant_uid: {str} -- self.variant_uid
39
+ :return: Entry, so you can chain this call.
40
+ """
41
+ headers = self.http_instance.headers.copy() # Create a local copy of headers
42
+ if isinstance(self.variant_uid, str):
43
+ headers['x-cs-variant-uid'] = self.variant_uid
44
+ elif isinstance(self.variant_uid, list):
45
+ headers['x-cs-variant-uid'] = ','.join(self.variant_uid)
46
+
47
+ if params is not None:
48
+ self.entry_param.update(params)
49
+ encoded_params = parse.urlencode(self.entry_param)
50
+ endpoint = self.http_instance.endpoint
51
+ url = f'{endpoint}/content_types/{self.content_type_id}/entries?{encoded_params}'
52
+ self.http_instance.headers.update(headers)
53
+ result = self.http_instance.get(url)
54
+ self.http_instance.headers.pop('x-cs-variant-uid', None)
55
+ return result
56
+
57
+ def fetch(self, params=None):
58
+ """
59
+ This method is useful to fetch variant entries of a particular content type and entries of the of the stack.
60
+ :return:dict -- contentType response
61
+ ------------------------------
62
+ Example:
63
+
64
+ >>> import contentstack
65
+ >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment')
66
+ >>> content_type = stack.content_type('content_type_uid')
67
+ >>> some_dict = {'abc':'something'}
68
+ >>> response = content_type.fetch(some_dict)
69
+ ------------------------------
70
+ """
71
+ """
72
+ Fetches the variants of the entry
73
+ :param self.variant_uid: {str} -- self.variant_uid
74
+ :return: Entry, so you can chain this call.
75
+ """
76
+ if self.entry_uid is None:
77
+ raise ValueError("entry_uid is required")
78
+ else:
79
+ headers = self.http_instance.headers.copy() # Create a local copy of headers
80
+ if isinstance(self.variant_uid, str):
81
+ headers['x-cs-variant-uid'] = self.variant_uid
82
+ elif isinstance(self.variant_uid, list):
83
+ headers['x-cs-variant-uid'] = ','.join(self.variant_uid)
84
+
85
+ if params is not None:
86
+ self.entry_param.update(params)
87
+ encoded_params = parse.urlencode(self.entry_param)
88
+ endpoint = self.http_instance.endpoint
89
+ url = f'{endpoint}/content_types/{self.content_type_id}/entries/{self.entry_uid}?{encoded_params}'
90
+ self.http_instance.headers.update(headers)
91
+ result = self.http_instance.get(url)
92
+ self.http_instance.headers.pop('x-cs-variant-uid', None)
93
+ return result
@@ -3,12 +3,12 @@ import unittest
3
3
  import config
4
4
  import contentstack
5
5
 
6
- _UID = 'blt53ca1231625bdde4'
7
6
  API_KEY = config.APIKEY
8
7
  DELIVERY_TOKEN = config.DELIVERYTOKEN
9
8
  ENVIRONMENT = config.ENVIRONMENT
10
9
  HOST = config.HOST
11
-
10
+ FAQ_UID = config.FAQ_UID # Add this in your config.py
11
+ VARIANT_UID = config.VARIANT_UID
12
12
 
13
13
  class TestEntry(unittest.TestCase):
14
14
 
@@ -19,69 +19,54 @@ class TestEntry(unittest.TestCase):
19
19
  query = self.stack.content_type('faq').query()
20
20
  result = query.find()
21
21
  if result is not None:
22
- self._UID = result['entries'][0]['uid']
23
- print(f'the uid is: {_UID}')
22
+ self.faq_uid = result['entries'][0]['uid']
23
+ print(f'the uid is: {self.faq_uid}')
24
24
 
25
25
  def test_entry_by_UID(self):
26
- global _UID
27
- entry = self.stack.content_type('faq').entry(_UID)
26
+ entry = self.stack.content_type('faq').entry(FAQ_UID)
28
27
  result = entry.fetch()
29
28
  if result is not None:
30
- _UID = result['entry']['uid']
31
- self.assertEqual(_UID, result['entry']['uid'])
29
+ self.assertEqual(FAQ_UID, result['entry']['uid'])
32
30
 
33
31
  def test_03_entry_environment(self):
34
- global _UID
35
- entry = self.stack.content_type('faq').entry(
36
- _UID).environment('test')
32
+ entry = self.stack.content_type('faq').entry(FAQ_UID).environment('test')
37
33
  self.assertEqual("test", entry.http_instance.headers['environment'])
38
34
 
39
35
  def test_04_entry_locale(self):
40
- global _UID
41
- entry = self.stack.content_type('faq').entry(_UID).locale('en-ei')
36
+ entry = self.stack.content_type('faq').entry(FAQ_UID).locale('en-ei')
42
37
  entry.fetch()
43
38
  self.assertEqual('en-ei', entry.entry_param['locale'])
44
39
 
45
40
  def test_05_entry_version(self):
46
- global _UID
47
- entry = self.stack.content_type('faq').entry(_UID).version(3)
41
+ entry = self.stack.content_type('faq').entry(FAQ_UID).version(3)
48
42
  entry.fetch()
49
43
  self.assertEqual(3, entry.entry_param['version'])
50
44
 
51
45
  def test_06_entry_params(self):
52
- global _UID
53
- entry = self.stack.content_type('faq').entry(
54
- _UID).param('param_key', 'param_value')
46
+ entry = self.stack.content_type('faq').entry(FAQ_UID).param('param_key', 'param_value')
55
47
  entry.fetch()
56
48
  self.assertEqual('param_value', entry.entry_param['param_key'])
57
49
 
58
50
  def test_07_entry_base_only(self):
59
- global _UID
60
- entry = self.stack.content_type(
61
- 'faq').entry(_UID).only('field_UID')
51
+ entry = self.stack.content_type('faq').entry(FAQ_UID).only('field_UID')
62
52
  entry.fetch()
63
53
  self.assertEqual({'environment': 'development',
64
54
  'only[BASE][]': 'field_UID'}, entry.entry_param)
65
55
 
66
56
  def test_08_entry_base_excepts(self):
67
- global _UID
68
- entry = self.stack.content_type('faq').entry(
69
- _UID).excepts('field_UID')
57
+ entry = self.stack.content_type('faq').entry(FAQ_UID).excepts('field_UID')
70
58
  entry.fetch()
71
59
  self.assertEqual({'environment': 'development',
72
60
  'except[BASE][]': 'field_UID'}, entry.entry_param)
73
61
 
74
62
  def test_10_entry_base_include_reference_only(self):
75
- global _UID
76
- entry = self.stack.content_type('faq').entry(_UID).only('field1')
63
+ entry = self.stack.content_type('faq').entry(FAQ_UID).only('field1')
77
64
  entry.fetch()
78
65
  self.assertEqual({'environment': 'development', 'only[BASE][]': 'field1'},
79
66
  entry.entry_param)
80
67
 
81
68
  def test_11_entry_base_include_reference_excepts(self):
82
- global _UID
83
- entry = self.stack.content_type(
84
- 'faq').entry(_UID).excepts('field1')
69
+ entry = self.stack.content_type('faq').entry(FAQ_UID).excepts('field1')
85
70
  entry.fetch()
86
71
  self.assertEqual({'environment': 'development', 'except[BASE][]': 'field1'},
87
72
  entry.entry_param)
@@ -95,15 +80,13 @@ class TestEntry(unittest.TestCase):
95
80
  response = _entry.fetch()
96
81
 
97
82
  def test_13_entry_support_include_fallback_unit_test(self):
98
- global _UID
99
- entry = self.stack.content_type('faq').entry(
100
- _UID).include_fallback()
83
+ entry = self.stack.content_type('faq').entry(FAQ_UID).include_fallback()
101
84
  self.assertEqual(
102
85
  True, entry.entry_param.__contains__('include_fallback'))
103
86
 
104
87
  def test_14_entry_queryable_only(self):
105
88
  try:
106
- entry = self.stack.content_type('faq').entry(_UID).only(4)
89
+ entry = self.stack.content_type('faq').entry(FAQ_UID).only(4)
107
90
  result = entry.fetch()
108
91
  self.assertEqual(None, result['uid'])
109
92
  except KeyError as e:
@@ -112,7 +95,7 @@ class TestEntry(unittest.TestCase):
112
95
 
113
96
  def test_entry_queryable_excepts(self):
114
97
  try:
115
- entry = self.stack.content_type('faq').entry(_UID).excepts(4)
98
+ entry = self.stack.content_type('faq').entry(FAQ_UID).excepts(4)
116
99
  result = entry.fetch()
117
100
  self.assertEqual(None, result['uid'])
118
101
  except KeyError as e:
@@ -120,20 +103,17 @@ class TestEntry(unittest.TestCase):
120
103
  self.assertEqual("Invalid field_UID provided", e.args[0])
121
104
 
122
105
  def test_16_entry_queryable_include_content_type(self):
123
- entry = self.stack.content_type('faq').entry(
124
- _UID).include_content_type()
106
+ entry = self.stack.content_type('faq').entry(FAQ_UID).include_content_type()
125
107
  self.assertEqual({'include_content_type': 'true', 'include_global_field_schema': 'true'},
126
108
  entry.entry_queryable_param)
127
109
 
128
110
  def test_reference_content_type_uid(self):
129
- entry = self.stack.content_type('faq').entry(
130
- _UID).include_reference_content_type_uid()
111
+ entry = self.stack.content_type('faq').entry(FAQ_UID).include_reference_content_type_uid()
131
112
  self.assertEqual({'include_reference_content_type_uid': 'true'},
132
113
  entry.entry_queryable_param)
133
114
 
134
115
  def test_19_entry_queryable_add_param(self):
135
- entry = self.stack.content_type('faq').entry(
136
- _UID).add_param('cms', 'contentstack')
116
+ entry = self.stack.content_type('faq').entry(FAQ_UID).add_param('cms', 'contentstack')
137
117
  self.assertEqual({'cms': 'contentstack'}, entry.entry_queryable_param)
138
118
 
139
119
  def test_20_entry_include_fallback(self):
@@ -154,6 +134,28 @@ class TestEntry(unittest.TestCase):
154
134
  content_type = self.stack.content_type('faq')
155
135
  entry = content_type.entry("878783238783").include_metadata()
156
136
  self.assertEqual({'include_metadata': 'true'}, entry.entry_queryable_param)
137
+
138
+ def test_23_content_type_variants(self):
139
+ content_type = self.stack.content_type('faq')
140
+ entry = content_type.variants(VARIANT_UID).find()
141
+ self.assertIn('variants', entry['entries'][0]['publish_details'])
142
+
143
+ def test_24_entry_variants(self):
144
+ content_type = self.stack.content_type('faq')
145
+ entry = content_type.entry(FAQ_UID).variants(VARIANT_UID).fetch()
146
+ self.assertIn('variants', entry['entry']['publish_details'])
147
+
148
+ def test_25_content_type_variants_with_has_hash_variant(self):
149
+ content_type = self.stack.content_type('faq')
150
+ entry = content_type.variants([VARIANT_UID]).find()
151
+ self.assertIn('variants', entry['entries'][0]['publish_details'])
152
+
153
+ def test_25_content_type_entry_variants_with_has_hash_variant(self):
154
+ content_type = self.stack.content_type('faq').entry(FAQ_UID)
155
+ entry = content_type.variants([VARIANT_UID]).fetch()
156
+ self.assertIn('variants', entry['entry']['publish_details'])
157
+
158
+
157
159
 
158
160
 
159
161
  if __name__ == '__main__':
@@ -4,9 +4,9 @@ import config
4
4
  import contentstack
5
5
  from contentstack.deep_merge_lp import DeepMergeMixin
6
6
 
7
- management_token = 'cs8743874323343u9'
8
- entry_uid = 'blt8743874323343u9'
9
- preview_token = 'abcdefgh1234567890'
7
+ management_token = config.MANAGEMENT_TOKEN
8
+ entry_uid = config.LIVE_PREVIEW_ENTRY_UID
9
+ preview_token = config.PREVIEW_TOKEN
10
10
 
11
11
  _lp_query = {
12
12
  'live_preview': '#0#0#0#0#0#0#0#0#0#',
@@ -131,11 +131,14 @@ class TestStack(unittest.TestCase):
131
131
  self.assertEqual(
132
132
  'is not valid.', result['errors']['pagination_token'][0])
133
133
 
134
- @unittest.skip('Work in progress')
135
- def test_16_initialise_sync(self):
136
- result = self.stack.sync_init()
137
- if result is not None:
138
- self.assertEqual(16, result['total_count'])
134
+ # Deprecated: This test was skipped due to deprecation of the sync_init feature or its API.
135
+ # If sync_init is permanently removed or unsupported, this test should remain commented or be deleted.
136
+ # If migration or replacement is planned, update this test accordingly.
137
+ # @unittest.skip('Work in progress')
138
+ # def test_16_initialise_sync(self):
139
+ # result = self.stack.sync_init()
140
+ # if result is not None:
141
+ # self.assertEqual(16, result['total_count'])
139
142
 
140
143
  def test_17_entry_with_sync_token(self):
141
144
  result = self.stack.sync_token('sync_token')
File without changes
File without changes
File without changes
File without changes