Skip to content

Commit 59b63cc

Browse files
Added branches support in entry variants
1 parent 6136eae commit 59b63cc

5 files changed

Lines changed: 220 additions & 37 deletions

File tree

CHANGELOG.md

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

33
## Content Management SDK For Python
4+
---
5+
## v1.9.0
6+
7+
#### Date: 18 May 2026
8+
9+
- Entry variants: added `publish` and `unpublish` for the entry publish/unpublish endpoints; documented payloads including `entry.variants` and optional `entry.variant_rules` on publish.
10+
- Entry variants: optional stack branch via the `branch` request header; `Entry.variants()` accepts no arguments, a branch UID only, or `(variant_uid, branch)` (use `variants(variant_uid, None)` when targeting a variant without a branch).
11+
412
---
513
## v1.8.1
614

contentstack_management/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
__author__ = 'dev-ex'
8383
__status__ = 'debug'
8484
__region__ = 'na'
85-
__version__ = '1.8.1'
85+
__version__ = '1.9.0'
8686
__host__ = 'api.contentstack.io'
8787
__protocol__ = 'https://'
8888
__api_version__ = 'v3'

contentstack_management/entries/entry.py

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -423,13 +423,15 @@ def unpublish(self, data):
423423
data = json.dumps(data)
424424
return self.client.post(url, headers = self.client.headers, data = data, params = self.params)
425425

426-
def variants(self, variant_uid: str = None):
426+
def variants(self, *args):
427427
"""
428428
Returns an EntryVariants instance for managing variant entries.
429-
430-
:param variant_uid: The `variant_uid` parameter is a string that represents the unique identifier of
431-
the variant. It is used to specify which variant to work with
432-
:type variant_uid: str
429+
430+
Pass no arguments when no ``branch`` header is required, one positional argument for the
431+
branch UID only (sent as the ``branch`` request header), or two
432+
arguments ``(variant_uid, branch)`` for a specific variant on a branch.
433+
434+
:param args: ``()`` | ``(branch,)`` | ``(variant_uid, branch)``
433435
:return: EntryVariants instance for managing variant entries
434436
-------------------------------
435437
[Example:]
@@ -438,13 +440,26 @@ def variants(self, variant_uid: str = None):
438440
>>> client = contentstack_management.Client(authtoken='your_authtoken')
439441
>>> # Get all variant entries
440442
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().query().find().json()
441-
>>> # Get specific variant entry
442-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').fetch().json()
443-
444-
-------------------------------
445-
"""
446-
447-
return EntryVariants(self.client, self.content_type_uid, self.entry_uid, variant_uid)
443+
>>> # Variant operations on a branch (branch header only)
444+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('branch_uid').query().find().json()
445+
>>> # Specific variant on a branch
446+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid', 'branch_uid').fetch().json()
447+
448+
-------------------------------
449+
"""
450+
variant_uid = None
451+
branch = None
452+
if len(args) == 1:
453+
branch = args[0]
454+
elif len(args) == 2:
455+
variant_uid, branch = args[0], args[1]
456+
elif len(args) > 2:
457+
raise TypeError(
458+
f"variants() takes at most 2 positional arguments ({len(args)} given)"
459+
)
460+
return EntryVariants(
461+
self.client, self.content_type_uid, self.entry_uid, variant_uid, branch
462+
)
448463

449464
def includeVariants(self, include_variants: str = 'true', variant_uid: str = None, params: dict = None):
450465
"""

contentstack_management/entry_variants/entry_variants.py

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,49 @@
11
"""This class takes a base URL as an argument when it's initialized,
22
which is the endpoint for the RESTFUL API that we'll be interacting with.
3-
The query(), create(), fetch(), delete(), update(), versions(), and includeVariants() methods each correspond to
3+
The query(), create(), fetch(), delete(), update(), versions(), includeVariants(), publish(), and unpublish() methods each correspond to
44
the operations that can be performed on the API """
55

66
import json
77
from ..common import Parameter
88
from .._errors import ArgumentException
9-
from .._messages import ENTRY_VARIANT_CONTENT_TYPE_UID_REQUIRED, ENTRY_VARIANT_ENTRY_UID_REQUIRED, ENTRY_VARIANT_UID_REQUIRED
9+
from .._messages import (
10+
ENTRY_BODY_REQUIRED,
11+
ENTRY_VARIANT_CONTENT_TYPE_UID_REQUIRED,
12+
ENTRY_VARIANT_ENTRY_UID_REQUIRED,
13+
ENTRY_VARIANT_UID_REQUIRED,
14+
)
1015

1116
class EntryVariants(Parameter):
1217
"""
1318
This class takes a base URL as an argument when it's initialized,
1419
which is the endpoint for the RESTFUL API that
15-
we'll be interacting with. The query(), create(), fetch(), delete(), update(), versions(), and includeVariants()
16-
methods each correspond to the operations that can be performed on the API """
20+
we'll be interacting with. The query(), create(), fetch(), delete(), update(), versions(),
21+
includeVariants(), publish(), and unpublish() methods each correspond to the operations that can be
22+
performed on the API. Optional ``branch`` scopes requests to a stack branch via the ``branch`` header. """
1723

18-
def __init__(self, client, content_type_uid: str, entry_uid: str, variant_uid: str = None):
24+
def __init__(
25+
self,
26+
client,
27+
content_type_uid: str,
28+
entry_uid: str,
29+
variant_uid: str = None,
30+
branch: str = None,
31+
):
1932
self.client = client
2033
self.content_type_uid = content_type_uid
2134
self.entry_uid = entry_uid
2235
self.variant_uid = variant_uid
36+
self.branch = branch if branch else None
2337
super().__init__(self.client)
2438
self.path = f"content_types/{content_type_uid}/entries/{entry_uid}/variants"
2539

40+
def _headers(self):
41+
"""Merge optional branch into request headers without mutating the client."""
42+
if not self.branch:
43+
return self.client.headers
44+
headers = dict(self.client.headers)
45+
headers["branch"] = self.branch
46+
return headers
2647

2748
def find(self, params: dict = None):
2849
"""
@@ -47,7 +68,7 @@ def find(self, params: dict = None):
4768
self.validate_entry_uid()
4869
if params is not None:
4970
self.params.update(params)
50-
return self.client.get(self.path, headers = self.client.headers, params = self.params)
71+
return self.client.get(self.path, headers=self._headers(), params=self.params)
5172

5273
def create(self, data: dict):
5374
"""
@@ -79,7 +100,7 @@ def create(self, data: dict):
79100
self.validate_content_type_uid()
80101
self.validate_entry_uid()
81102
data = json.dumps(data)
82-
return self.client.post(self.path, headers = self.client.headers, data=data, params = self.params)
103+
return self.client.post(self.path, headers=self._headers(), data=data, params=self.params)
83104

84105
def fetch(self, variant_uid: str = None, params: dict = None):
85106
"""
@@ -96,9 +117,11 @@ def fetch(self, variant_uid: str = None, params: dict = None):
96117
97118
>>> import contentstack_management
98119
>>> client = contentstack_management.Client(authtoken='your_authtoken')
99-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').fetch().json()
120+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().fetch('variant_uid').json()
100121
>>> # With parameters
101-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').fetch(params={'include_count': True}).json()
122+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().fetch('variant_uid', params={'include_count': True}).json()
123+
>>> # On a branch
124+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid', 'branch_uid').fetch().json()
102125
103126
-------------------------------
104127
"""
@@ -112,7 +135,7 @@ def fetch(self, variant_uid: str = None, params: dict = None):
112135
if params is not None:
113136
self.params.update(params)
114137
url = f"{self.path}/{self.variant_uid}"
115-
return self.client.get(url, headers = self.client.headers, params = self.params)
138+
return self.client.get(url, headers=self._headers(), params=self.params)
116139

117140
def delete(self, variant_uid: str = None):
118141
"""
@@ -128,7 +151,7 @@ def delete(self, variant_uid: str = None):
128151
129152
>>> import contentstack_management
130153
>>> client = contentstack_management.Client(authtoken='your_authtoken')
131-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').delete().json()
154+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().delete('variant_uid').json()
132155
133156
-------------------------------
134157
"""
@@ -138,7 +161,7 @@ def delete(self, variant_uid: str = None):
138161
self.validate_entry_uid()
139162
self.validate_variant_uid()
140163
url = f"{self.path}/{self.variant_uid}"
141-
return self.client.delete(url, headers = self.client.headers, params = self.params)
164+
return self.client.delete(url, headers=self._headers(), params=self.params)
142165

143166
def update(self, data: dict, variant_uid: str = None):
144167
"""
@@ -167,7 +190,7 @@ def update(self, data: dict, variant_uid: str = None):
167190
>>> }
168191
>>> import contentstack_management
169192
>>> client = contentstack_management.Client(authtoken='your_authtoken')
170-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').update(data).json()
193+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().update(data, 'variant_uid').json()
171194
172195
-------------------------------
173196
"""
@@ -178,7 +201,7 @@ def update(self, data: dict, variant_uid: str = None):
178201
self.validate_variant_uid()
179202
url = f"{self.path}/{self.variant_uid}"
180203
data = json.dumps(data)
181-
return self.client.put(url, headers = self.client.headers, data=data, params = self.params)
204+
return self.client.put(url, headers=self._headers(), data=data, params=self.params)
182205

183206
def versions(self, variant_uid: str = None, params: dict = None):
184207
"""
@@ -195,9 +218,9 @@ def versions(self, variant_uid: str = None, params: dict = None):
195218
196219
>>> import contentstack_management
197220
>>> client = contentstack_management.Client(authtoken='your_authtoken')
198-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').versions().json()
221+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().versions('variant_uid').json()
199222
>>> # With parameters
200-
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants('variant_uid').versions(params={'limit': 10}).json()
223+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().versions('variant_uid', params={'limit': 10}).json()
201224
202225
-------------------------------
203226
"""
@@ -209,7 +232,7 @@ def versions(self, variant_uid: str = None, params: dict = None):
209232
if params is not None:
210233
self.params.update(params)
211234
url = f"{self.path}/{self.variant_uid}/versions"
212-
return self.client.get(url, headers = self.client.headers, params = self.params)
235+
return self.client.get(url, headers=self._headers(), params=self.params)
213236

214237
def includeVariants(self, include_variants: str = 'true', variant_uid: str = None, params: dict = None):
215238
"""
@@ -243,8 +266,85 @@ def includeVariants(self, include_variants: str = 'true', variant_uid: str = Non
243266
self.params.update(params)
244267
self.params['include_variants'] = include_variants
245268
url = f"content_types/{self.content_type_uid}/entries/{self.entry_uid}"
246-
return self.client.get(url, headers = self.client.headers, params = self.params)
269+
return self.client.get(url, headers=self._headers(), params=self.params)
247270

271+
def publish(self, data: dict):
272+
"""
273+
Publish the entry for this content type and entry UID (CMA ``.../entries/{entry_uid}/publish``).
274+
275+
For entry variants, the body typically includes ``entry.environments``, ``entry.locales``,
276+
``entry.variants`` (list of ``{"uid", "version"}`` per variant), optional ``entry.variant_rules``,
277+
and top-level ``locale`` (and optional scheduling fields supported by the API).
278+
279+
:param data: Publish payload dict (serialized as JSON).
280+
:return: Response from the publish request.
281+
-------------------------------
282+
[Example:]
283+
284+
>>> data = {
285+
>>> "entry": {
286+
>>> "environments": ["production"],
287+
>>> "locales": ["en-us"],
288+
>>> "variants": [
289+
>>> {"uid": "variant_uid", "version": 1}
290+
>>> ],
291+
>>> "variant_rules": {
292+
>>> "publish_latest_base": False,
293+
>>> "publish_latest_base_conditionally": True
294+
>>> }
295+
>>> },
296+
>>> "locale": "en-us"
297+
>>> }
298+
>>> import contentstack_management
299+
>>> client = contentstack_management.Client(authtoken='your_authtoken')
300+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().publish(data).json()
301+
302+
-------------------------------
303+
"""
304+
self.validate_content_type_uid()
305+
self.validate_entry_uid()
306+
if data is None:
307+
raise Exception(ENTRY_BODY_REQUIRED)
308+
url = f"content_types/{self.content_type_uid}/entries/{self.entry_uid}/publish"
309+
data = json.dumps(data)
310+
return self.client.post(url, headers=self._headers(), data=data, params=self.params)
311+
312+
def unpublish(self, data: dict):
313+
"""
314+
Unpublish the entry for this content type and entry UID (CMA ``.../entries/{entry_uid}/unpublish``).
315+
316+
For entry variants, the body typically includes ``entry.environments``, ``entry.locales``,
317+
``entry.variants`` (list of ``{"uid", "version"}`` per variant), and top-level ``locale``.
318+
319+
:param data: Unpublish payload dict (serialized as JSON).
320+
:return: Response from the unpublish request.
321+
-------------------------------
322+
[Example:]
323+
324+
>>> data = {
325+
>>> "entry": {
326+
>>> "environments": ["environment_uid"],
327+
>>> "locales": ["en-us"],
328+
>>> "variants": [
329+
>>> {"uid": "variant_uid", "version": 1}
330+
>>> ]
331+
>>> },
332+
>>> "locale": "en-us"
333+
>>> }
334+
>>> import contentstack_management
335+
>>> client = contentstack_management.Client(authtoken='your_authtoken')
336+
>>> result = client.stack('api_key').content_types('content_type_uid').entry('entry_uid').variants().unpublish(data).json()
337+
338+
-------------------------------
339+
"""
340+
self.validate_content_type_uid()
341+
self.validate_entry_uid()
342+
if data is None:
343+
raise Exception(ENTRY_BODY_REQUIRED)
344+
url = f"content_types/{self.content_type_uid}/entries/{self.entry_uid}/unpublish"
345+
data = json.dumps(data)
346+
return self.client.post(url, headers=self._headers(), data=data, params=self.params)
347+
248348
def validate_content_type_uid(self):
249349
"""
250350
The function checks if the content_type_uid is None or an empty string and raises an ArgumentException

0 commit comments

Comments
 (0)