Contentstack 2.7.1__tar.gz → 2.8.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 (47) hide show
  1. {contentstack-2.7.1 → contentstack-2.8.0}/Contentstack.egg-info/PKG-INFO +31 -1
  2. {contentstack-2.7.1 → contentstack-2.8.0}/Contentstack.egg-info/SOURCES.txt +2 -0
  3. {contentstack-2.7.1 → contentstack-2.8.0}/PKG-INFO +31 -1
  4. {contentstack-2.7.1 → contentstack-2.8.0}/README.md +30 -0
  5. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/__init__.py +1 -1
  6. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/stack.py +28 -6
  7. contentstack-2.8.0/contentstack/taxonomy.py +233 -0
  8. contentstack-2.8.0/contentstack/term.py +194 -0
  9. contentstack-2.8.0/tests/test_taxonomies.py +733 -0
  10. contentstack-2.8.0/tests/test_taxonomies_unit.py +283 -0
  11. contentstack-2.7.1/contentstack/taxonomy.py +0 -64
  12. contentstack-2.7.1/tests/test_taxonomies.py +0 -246
  13. {contentstack-2.7.1 → contentstack-2.8.0}/Contentstack.egg-info/dependency_links.txt +0 -0
  14. {contentstack-2.7.1 → contentstack-2.8.0}/Contentstack.egg-info/not-zip-safe +0 -0
  15. {contentstack-2.7.1 → contentstack-2.8.0}/Contentstack.egg-info/requires.txt +0 -0
  16. {contentstack-2.7.1 → contentstack-2.8.0}/Contentstack.egg-info/top_level.txt +0 -0
  17. {contentstack-2.7.1 → contentstack-2.8.0}/LICENSE +0 -0
  18. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/asset.py +0 -0
  19. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/assetquery.py +0 -0
  20. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/basequery.py +0 -0
  21. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/contenttype.py +0 -0
  22. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/controller.py +0 -0
  23. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/deep_merge_lp.py +0 -0
  24. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/endpoint.py +0 -0
  25. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/entry.py +0 -0
  26. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/entryqueryable.py +0 -0
  27. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/error_messages.py +0 -0
  28. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/globalfields.py +0 -0
  29. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/https_connection.py +0 -0
  30. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/image_transform.py +0 -0
  31. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/query.py +0 -0
  32. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/region_refresh.py +0 -0
  33. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/utility.py +0 -0
  34. {contentstack-2.7.1 → contentstack-2.8.0}/contentstack/variants.py +0 -0
  35. {contentstack-2.7.1 → contentstack-2.8.0}/setup.cfg +0 -0
  36. {contentstack-2.7.1 → contentstack-2.8.0}/setup.py +0 -0
  37. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_assets.py +0 -0
  38. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_basequery.py +0 -0
  39. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_early_fetch.py +0 -0
  40. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_early_find.py +0 -0
  41. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_endpoint.py +0 -0
  42. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_entry.py +0 -0
  43. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_global_fields.py +0 -0
  44. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_live_preview.py +0 -0
  45. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_query.py +0 -0
  46. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_stack.py +0 -0
  47. {contentstack-2.7.1 → contentstack-2.8.0}/tests/test_variants.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Contentstack
3
- Version: 2.7.1
3
+ Version: 2.8.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
@@ -162,6 +162,36 @@ image = stack.image_transform(url, {'width': 100, 'height': 100}).get_url()
162
162
  image = stack.image_transform(url, {'auto': 'webp'}).get_url()
163
163
  ```
164
164
 
165
+ ### Working with Taxonomies
166
+
167
+ Taxonomies published to an environment/locale can be fetched directly via the Content Delivery API.
168
+
169
+ ```python
170
+ # List all published taxonomies
171
+ result = stack.taxonomy().limit(10).include_count().find()
172
+ taxonomies = result['taxonomies']
173
+
174
+ # Fetch a single taxonomy, with locale fallback
175
+ taxonomy = stack.taxonomy('regions').locale('fr-fr').include_fallback().fetch()
176
+
177
+ # List all terms in a taxonomy
178
+ terms_result = stack.taxonomy('regions').term().locale('en-us').depth(3).find()
179
+
180
+ # Fetch a single term
181
+ term = stack.taxonomy('regions').term('california').fetch()
182
+
183
+ # Term hierarchy traversal
184
+ ancestors = stack.taxonomy('regions').term('san-francisco').depth(5).ancestors()
185
+ descendants = stack.taxonomy('electronics').term('laptops').depth(2).descendants()
186
+ locales = stack.taxonomy('regions').term('california').locales()
187
+ ```
188
+
189
+ `stack.taxonomy()` (no uid) with a filter chained still filters entries by taxonomy, unchanged:
190
+
191
+ ```python
192
+ result = stack.taxonomy().in_('taxonomies.category', ['category1', 'category2']).find()
193
+ ```
194
+
165
195
  ### Using the Sync API with Python SDK
166
196
 
167
197
  The Sync API takes care of syncing your Contentstack data with your application and ensures that the data is always
@@ -25,6 +25,7 @@ contentstack/query.py
25
25
  contentstack/region_refresh.py
26
26
  contentstack/stack.py
27
27
  contentstack/taxonomy.py
28
+ contentstack/term.py
28
29
  contentstack/utility.py
29
30
  contentstack/variants.py
30
31
  tests/test_assets.py
@@ -38,4 +39,5 @@ tests/test_live_preview.py
38
39
  tests/test_query.py
39
40
  tests/test_stack.py
40
41
  tests/test_taxonomies.py
42
+ tests/test_taxonomies_unit.py
41
43
  tests/test_variants.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Contentstack
3
- Version: 2.7.1
3
+ Version: 2.8.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
@@ -162,6 +162,36 @@ image = stack.image_transform(url, {'width': 100, 'height': 100}).get_url()
162
162
  image = stack.image_transform(url, {'auto': 'webp'}).get_url()
163
163
  ```
164
164
 
165
+ ### Working with Taxonomies
166
+
167
+ Taxonomies published to an environment/locale can be fetched directly via the Content Delivery API.
168
+
169
+ ```python
170
+ # List all published taxonomies
171
+ result = stack.taxonomy().limit(10).include_count().find()
172
+ taxonomies = result['taxonomies']
173
+
174
+ # Fetch a single taxonomy, with locale fallback
175
+ taxonomy = stack.taxonomy('regions').locale('fr-fr').include_fallback().fetch()
176
+
177
+ # List all terms in a taxonomy
178
+ terms_result = stack.taxonomy('regions').term().locale('en-us').depth(3).find()
179
+
180
+ # Fetch a single term
181
+ term = stack.taxonomy('regions').term('california').fetch()
182
+
183
+ # Term hierarchy traversal
184
+ ancestors = stack.taxonomy('regions').term('san-francisco').depth(5).ancestors()
185
+ descendants = stack.taxonomy('electronics').term('laptops').depth(2).descendants()
186
+ locales = stack.taxonomy('regions').term('california').locales()
187
+ ```
188
+
189
+ `stack.taxonomy()` (no uid) with a filter chained still filters entries by taxonomy, unchanged:
190
+
191
+ ```python
192
+ result = stack.taxonomy().in_('taxonomies.category', ['category1', 'category2']).find()
193
+ ```
194
+
165
195
  ### Using the Sync API with Python SDK
166
196
 
167
197
  The Sync API takes care of syncing your Contentstack data with your application and ensures that the data is always
@@ -126,6 +126,36 @@ image = stack.image_transform(url, {'width': 100, 'height': 100}).get_url()
126
126
  image = stack.image_transform(url, {'auto': 'webp'}).get_url()
127
127
  ```
128
128
 
129
+ ### Working with Taxonomies
130
+
131
+ Taxonomies published to an environment/locale can be fetched directly via the Content Delivery API.
132
+
133
+ ```python
134
+ # List all published taxonomies
135
+ result = stack.taxonomy().limit(10).include_count().find()
136
+ taxonomies = result['taxonomies']
137
+
138
+ # Fetch a single taxonomy, with locale fallback
139
+ taxonomy = stack.taxonomy('regions').locale('fr-fr').include_fallback().fetch()
140
+
141
+ # List all terms in a taxonomy
142
+ terms_result = stack.taxonomy('regions').term().locale('en-us').depth(3).find()
143
+
144
+ # Fetch a single term
145
+ term = stack.taxonomy('regions').term('california').fetch()
146
+
147
+ # Term hierarchy traversal
148
+ ancestors = stack.taxonomy('regions').term('san-francisco').depth(5).ancestors()
149
+ descendants = stack.taxonomy('electronics').term('laptops').depth(2).descendants()
150
+ locales = stack.taxonomy('regions').term('california').locales()
151
+ ```
152
+
153
+ `stack.taxonomy()` (no uid) with a filter chained still filters entries by taxonomy, unchanged:
154
+
155
+ ```python
156
+ result = stack.taxonomy().in_('taxonomies.category', ['category1', 'category2']).find()
157
+ ```
158
+
129
159
  ### Using the Sync API with Python SDK
130
160
 
131
161
  The Sync API takes care of syncing your Contentstack data with your application and ensures that the data is always
@@ -42,7 +42,7 @@ def get_contentstack_endpoint(region='us', service='', omit_https=False):
42
42
  __title__ = 'contentstack-delivery-python'
43
43
  __author__ = 'contentstack'
44
44
  __status__ = 'debug'
45
- __version__ = 'v2.7.1'
45
+ __version__ = 'v2.8.0'
46
46
  __endpoint__ = 'cdn.contentstack.io'
47
47
  __email__ = 'support@contentstack.com'
48
48
  __developer_email__ = 'mobile@contentstack.com'
@@ -8,7 +8,7 @@ from contentstack.asset import Asset
8
8
  from contentstack.assetquery import AssetQuery
9
9
  from contentstack.contenttype import ContentType
10
10
  from contentstack.endpoint import Endpoint
11
- from contentstack.taxonomy import Taxonomy
11
+ from contentstack.taxonomy import Taxonomy, TaxonomyQuery
12
12
  from contentstack.globalfields import GlobalField
13
13
  from contentstack.https_connection import HTTPSConnection
14
14
  from contentstack.image_transform import ImageTransform
@@ -202,13 +202,35 @@ class Stack:
202
202
  """
203
203
  return ContentType(self.http_instance, content_type_uid)
204
204
 
205
- def taxonomy(self):
205
+ def taxonomy(self, taxonomy_uid: str = None):
206
206
  """
207
- taxonomy defines the structure or schema of a page or a section
208
- of your web or mobile property.
209
- :return: taxonomy
207
+ Without taxonomy_uid: returns a TaxonomyQuery. Chain a filter
208
+ (in_, or_, and_, exists, above, below, equal_and_above, equal_and_below)
209
+ then find() to fetch entries filtered by taxonomy (legacy behavior,
210
+ unchanged). Or call find() directly with nothing chained to list all
211
+ published taxonomies from the Content Delivery API.
212
+
213
+ With taxonomy_uid: returns a Taxonomy for CDA access to that specific
214
+ published taxonomy (fetch(), term()).
215
+
216
+ :param taxonomy_uid: {str} -- (optional) unique identifier of the taxonomy.
217
+ :return: TaxonomyQuery or Taxonomy
218
+ -----------------------------
219
+ Example::
220
+
221
+ >>> import contentstack
222
+ >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment')
223
+ >>> # Legacy: filter entries by taxonomy
224
+ >>> result = stack.taxonomy().in_('taxonomies.color', ['red']).find()
225
+ >>> # New: list all published taxonomies
226
+ >>> result = stack.taxonomy().limit(10).find()
227
+ >>> # New: fetch a single published taxonomy
228
+ >>> result = stack.taxonomy('regions').fetch()
229
+ -----------------------------
210
230
  """
211
- return Taxonomy(self.http_instance)
231
+ if taxonomy_uid:
232
+ return Taxonomy(self.http_instance, taxonomy_uid)
233
+ return TaxonomyQuery(self.http_instance)
212
234
 
213
235
  def global_field(self, global_field_uid=None):
214
236
  """
@@ -0,0 +1,233 @@
1
+ import json
2
+ from urllib.parse import quote
3
+
4
+ from contentstack.error_messages import ErrorMessages
5
+ from contentstack.utility import Utils
6
+
7
+
8
+ class TaxonomyFilter:
9
+ """
10
+ Taxonomy entry-filter queries ($above, $below, $in, $exists, ...).
11
+ Use stack.taxonomy() to instantiate.
12
+
13
+ API Reference: https://www.contentstack.com/docs/developers/apis/content-delivery-api/#taxonomies
14
+ """
15
+
16
+ def __init__(self, http_instance):
17
+ self.http_instance = http_instance
18
+ self._filters: dict = {}
19
+
20
+ def _add(self, field: str, condition: dict) -> "TaxonomyFilter":
21
+ self._filters[field] = condition
22
+ return self
23
+
24
+ def in_(self, field: str, terms: list) -> "TaxonomyFilter":
25
+ return self._add(field, {"$in": terms})
26
+
27
+ def or_(self, *conds: dict) -> "TaxonomyFilter":
28
+ return self._add("$or", list(conds))
29
+
30
+ def and_(self, *conds: dict) -> "TaxonomyFilter":
31
+ return self._add("$and", list(conds))
32
+
33
+ def exists(self, field: str) -> "TaxonomyFilter":
34
+ return self._add(field, {"$exists": True})
35
+
36
+ def equal_and_below(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyFilter":
37
+ cond = {"$eq_below": term_uid, "levels": levels}
38
+ return self._add(field, cond)
39
+
40
+ def below(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyFilter":
41
+ cond = {"$below": term_uid, "levels": levels}
42
+ return self._add(field, cond)
43
+
44
+ def equal_and_above(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyFilter":
45
+ cond = {"$eq_above": term_uid, "levels": levels}
46
+ return self._add(field, cond)
47
+
48
+ def above(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyFilter":
49
+ cond = {"$above": term_uid, "levels": levels}
50
+ return self._add(field, cond)
51
+
52
+ def find(self, params=None):
53
+ """
54
+ This method fetches entries filtered by taxonomy from the stack.
55
+ """
56
+ self.local_param = {}
57
+ self.local_param['environment'] = self.http_instance.headers['environment']
58
+
59
+ # Ensure query param is always present
60
+ query_string = json.dumps(self._filters or {})
61
+ query_encoded = quote(query_string, safe='{}":,[]') # preserves JSON characters
62
+
63
+ # Build the base URL
64
+ endpoint = self.http_instance.endpoint
65
+ url = f'{endpoint}/taxonomies/entries?environment={self.local_param["environment"]}&query={query_encoded}'
66
+
67
+ # Append any additional params manually
68
+ if params:
69
+ other_params = '&'.join(f'{k}={v}' for k, v in params.items())
70
+ url += f'&{other_params}'
71
+ return self.http_instance.get(url)
72
+
73
+
74
+ class TaxonomyQuery(TaxonomyFilter):
75
+ """
76
+ stack.taxonomy() — no uid.
77
+
78
+ Chain a filter (in_, or_, and_, exists, above, below, equal_and_above,
79
+ equal_and_below) then call find() to fetch entries filtered by taxonomy
80
+ (unchanged legacy behavior, inherited from TaxonomyFilter).
81
+
82
+ Call find() directly with nothing chained to list all published
83
+ taxonomies from the Content Delivery API instead.
84
+
85
+ Example::
86
+
87
+ >>> import contentstack
88
+ >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment')
89
+ >>> # Legacy: filter entries by taxonomy
90
+ >>> result = stack.taxonomy().in_('taxonomies.color', ['red']).find()
91
+ >>> # New: list all published taxonomies
92
+ >>> result = stack.taxonomy().limit(10).include_count().find()
93
+ """
94
+
95
+ def __init__(self, http_instance):
96
+ super().__init__(http_instance)
97
+ self._query_params = {}
98
+
99
+ def skip(self, skip: int) -> "TaxonomyQuery":
100
+ """Sets pagination offset for listing taxonomies.
101
+ :param skip: {int} -- number of taxonomies to skip
102
+ :return: TaxonomyQuery, so you can chain this call.
103
+ """
104
+ self._query_params['skip'] = skip
105
+ return self
106
+
107
+ def limit(self, limit: int) -> "TaxonomyQuery":
108
+ """Sets the maximum number of taxonomies to return.
109
+ :param limit: {int} -- max number of taxonomies to return
110
+ :return: TaxonomyQuery, so you can chain this call.
111
+ """
112
+ self._query_params['limit'] = limit
113
+ return self
114
+
115
+ def include_count(self) -> "TaxonomyQuery":
116
+ """Includes the total count of taxonomies in the response.
117
+ :return: TaxonomyQuery, so you can chain this call.
118
+ """
119
+ self._query_params['include_count'] = 'true'
120
+ return self
121
+
122
+ def param(self, key: str, value) -> "TaxonomyQuery":
123
+ """Adds an arbitrary query parameter to the list-taxonomies request
124
+ (e.g. 'locale').
125
+ :param key: {str} -- query parameter key
126
+ :param value: value for the query parameter
127
+ :return: TaxonomyQuery, so you can chain this call.
128
+ """
129
+ if None in (key, value):
130
+ raise KeyError(ErrorMessages.INVALID_KEY_VALUE_ARGS)
131
+ self._query_params[key] = value
132
+ return self
133
+
134
+ def find(self, params=None):
135
+ """
136
+ If a filter was chained (in_/or_/and_/exists/above/below/...), fetches
137
+ entries filtered by taxonomy — unchanged legacy behavior.
138
+ GET /taxonomies/entries?environment=...&query=...
139
+
140
+ Otherwise, fetches all published taxonomies from the CDA.
141
+ GET /taxonomies
142
+
143
+ :return: dict -- filtered entries, or {'taxonomies': [...], 'count'?: n}
144
+ """
145
+ if self._filters:
146
+ return super().find(params)
147
+ if params:
148
+ self._query_params.update(params)
149
+ url = f'{self.http_instance.endpoint}/taxonomies'
150
+ query_str = Utils.do_url_encode(self._query_params)
151
+ return self.http_instance.get(f'{url}?{query_str}' if query_str else url)
152
+
153
+
154
+ class Taxonomy:
155
+ """
156
+ Represents a single published taxonomy from the Content Delivery API.
157
+ Use stack.taxonomy(uid) to instantiate.
158
+
159
+ Example::
160
+
161
+ >>> import contentstack
162
+ >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment')
163
+ >>> taxonomy = stack.taxonomy('regions').locale('fr-fr').include_fallback()
164
+ >>> result = taxonomy.fetch()
165
+ """
166
+
167
+ def __init__(self, http_instance, taxonomy_uid: str):
168
+ if not taxonomy_uid:
169
+ raise KeyError(ErrorMessages.INVALID_KEY_OR_VALUE)
170
+ self.http_instance = http_instance
171
+ self._taxonomy_uid = taxonomy_uid
172
+ self._url = f'{http_instance.endpoint}/taxonomies/{taxonomy_uid}'
173
+ self._query_params = {}
174
+
175
+ def term(self, term_uid: str = None):
176
+ """
177
+ Without term_uid: returns TermQuery for listing all terms in this taxonomy.
178
+ With term_uid: returns Term for a specific term.
179
+
180
+ :param term_uid: {str} -- (optional) unique identifier of the term.
181
+ :return: Term or TermQuery
182
+ """
183
+ from contentstack.term import Term, TermQuery
184
+ if term_uid:
185
+ return Term(self.http_instance, self._taxonomy_uid, term_uid)
186
+ return TermQuery(self.http_instance, self._taxonomy_uid)
187
+
188
+ def locale(self, locale: str) -> "Taxonomy":
189
+ """Sets the locale for this taxonomy fetch (e.g. 'en-us', 'fr-fr').
190
+ :param locale: {str} -- locale code
191
+ :return: Taxonomy, so you can chain this call.
192
+ """
193
+ self._query_params['locale'] = locale
194
+ return self
195
+
196
+ def include_fallback(self) -> "Taxonomy":
197
+ """Enables locale fallback through the branch hierarchy.
198
+ If the taxonomy is not published in the requested locale, falls back
199
+ to the parent locale in the branch hierarchy.
200
+ :return: Taxonomy, so you can chain this call.
201
+ """
202
+ self._query_params['include_fallback'] = 'true'
203
+ return self
204
+
205
+ def include_branch(self) -> "Taxonomy":
206
+ """Includes the _branch field in the response.
207
+ :return: Taxonomy, so you can chain this call.
208
+ """
209
+ self._query_params['include_branch'] = 'true'
210
+ return self
211
+
212
+ def param(self, key: str, value) -> "Taxonomy":
213
+ """Adds an arbitrary query parameter.
214
+ :param key: {str} -- query parameter key
215
+ :param value: value for the query parameter
216
+ :return: Taxonomy, so you can chain this call.
217
+ """
218
+ if None in (key, value):
219
+ raise KeyError(ErrorMessages.INVALID_KEY_VALUE_ARGS)
220
+ self._query_params[key] = value
221
+ return self
222
+
223
+ def fetch(self) -> dict:
224
+ """
225
+ Fetches this taxonomy from the CDA.
226
+ GET /taxonomies/{taxonomy_uid}
227
+
228
+ :return: dict -- the taxonomy object.
229
+ """
230
+ query_str = Utils.do_url_encode(self._query_params)
231
+ url = f'{self._url}?{query_str}' if query_str else self._url
232
+ response = self.http_instance.get(url)
233
+ return response.get('taxonomy', response)
@@ -0,0 +1,194 @@
1
+ from contentstack.error_messages import ErrorMessages
2
+ from contentstack.utility import Utils
3
+
4
+
5
+ class Term:
6
+ """
7
+ Represents a single published taxonomy term.
8
+ Supports hierarchy traversal: ancestors(), descendants(), locales().
9
+ Use stack.taxonomy(uid).term(term_uid) to instantiate.
10
+
11
+ Example::
12
+
13
+ >>> import contentstack
14
+ >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment')
15
+ >>> term = stack.taxonomy('regions').term('california')
16
+ >>> result = term.fetch()
17
+ """
18
+
19
+ def __init__(self, http_instance, taxonomy_uid: str, term_uid: str):
20
+ if not term_uid:
21
+ raise KeyError(ErrorMessages.INVALID_KEY_OR_VALUE)
22
+ self.http_instance = http_instance
23
+ self._url = f'{http_instance.endpoint}/taxonomies/{taxonomy_uid}/terms/{term_uid}'
24
+ self._query_params = {}
25
+
26
+ def depth(self, depth: int) -> "Term":
27
+ """Limits the depth of hierarchy traversal for ancestors()/descendants().
28
+ :param depth: {int} -- maximum hierarchy depth
29
+ :return: Term, so you can chain this call.
30
+ """
31
+ self._query_params['depth'] = depth
32
+ return self
33
+
34
+ def locale(self, locale: str) -> "Term":
35
+ """Sets the locale for this term fetch (e.g. 'en-us', 'fr-fr').
36
+ :param locale: {str} -- locale code
37
+ :return: Term, so you can chain this call.
38
+ """
39
+ self._query_params['locale'] = locale
40
+ return self
41
+
42
+ def include_fallback(self) -> "Term":
43
+ """Enables locale fallback through the branch hierarchy.
44
+ :return: Term, so you can chain this call.
45
+ """
46
+ self._query_params['include_fallback'] = 'true'
47
+ return self
48
+
49
+ def include_branch(self) -> "Term":
50
+ """Includes the _branch field in the response.
51
+ :return: Term, so you can chain this call.
52
+ """
53
+ self._query_params['include_branch'] = 'true'
54
+ return self
55
+
56
+ def param(self, key: str, value) -> "Term":
57
+ """Adds an arbitrary query parameter.
58
+ :param key: {str} -- query parameter key
59
+ :param value: value for the query parameter
60
+ :return: Term, so you can chain this call.
61
+ """
62
+ if None in (key, value):
63
+ raise KeyError(ErrorMessages.INVALID_KEY_VALUE_ARGS)
64
+ self._query_params[key] = value
65
+ return self
66
+
67
+ def _get(self, suffix: str = '', key: str = None):
68
+ url = f'{self._url}{suffix}'
69
+ query_str = Utils.do_url_encode(self._query_params)
70
+ response = self.http_instance.get(f'{url}?{query_str}' if query_str else url)
71
+ return response.get(key, response) if key else response
72
+
73
+ def fetch(self) -> dict:
74
+ """
75
+ Fetches this term.
76
+ GET /taxonomies/{taxonomy_uid}/terms/{term_uid}
77
+ :return: dict -- the term object.
78
+ """
79
+ return self._get(key='term')
80
+
81
+ def locales(self) -> dict:
82
+ """
83
+ Fetches all published localized versions of this term.
84
+ GET /taxonomies/{taxonomy_uid}/terms/{term_uid}/locales
85
+ :return: dict -- the localized terms.
86
+ """
87
+ return self._get('/locales', key='terms')
88
+
89
+ def ancestors(self) -> list:
90
+ """
91
+ Fetches all ancestor terms up to the root.
92
+ GET /taxonomies/{taxonomy_uid}/terms/{term_uid}/ancestors
93
+ :return: list -- the ancestor terms.
94
+ """
95
+ return self._get('/ancestors', key='terms')
96
+
97
+ def descendants(self) -> list:
98
+ """
99
+ Fetches all descendant terms.
100
+ GET /taxonomies/{taxonomy_uid}/terms/{term_uid}/descendants
101
+ :return: list -- the descendant terms.
102
+ """
103
+ return self._get('/descendants', key='terms')
104
+
105
+
106
+ class TermQuery:
107
+ """
108
+ Query builder for fetching all terms in a taxonomy.
109
+ Use stack.taxonomy(uid).term() to instantiate.
110
+
111
+ Example::
112
+
113
+ >>> import contentstack
114
+ >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment')
115
+ >>> result = stack.taxonomy('regions').term().locale('en-us').depth(3).find()
116
+ """
117
+
118
+ def __init__(self, http_instance, taxonomy_uid: str):
119
+ self.http_instance = http_instance
120
+ self._url = f'{http_instance.endpoint}/taxonomies/{taxonomy_uid}/terms'
121
+ self._query_params = {}
122
+
123
+ def depth(self, depth: int) -> "TermQuery":
124
+ """Limits term hierarchy traversal depth.
125
+ :param depth: {int} -- maximum hierarchy depth
126
+ :return: TermQuery, so you can chain this call.
127
+ """
128
+ self._query_params['depth'] = depth
129
+ return self
130
+
131
+ def skip(self, skip: int) -> "TermQuery":
132
+ """Sets pagination offset.
133
+ :param skip: {int} -- number of terms to skip
134
+ :return: TermQuery, so you can chain this call.
135
+ """
136
+ self._query_params['skip'] = skip
137
+ return self
138
+
139
+ def limit(self, limit: int) -> "TermQuery":
140
+ """Sets the maximum number of terms to return.
141
+ :param limit: {int} -- max number of terms to return
142
+ :return: TermQuery, so you can chain this call.
143
+ """
144
+ self._query_params['limit'] = limit
145
+ return self
146
+
147
+ def locale(self, locale: str) -> "TermQuery":
148
+ """Filters terms by locale (e.g. 'en-us', 'fr-fr').
149
+ :param locale: {str} -- locale code
150
+ :return: TermQuery, so you can chain this call.
151
+ """
152
+ self._query_params['locale'] = locale
153
+ return self
154
+
155
+ def include_fallback(self) -> "TermQuery":
156
+ """Enables locale fallback through the branch hierarchy.
157
+ :return: TermQuery, so you can chain this call.
158
+ """
159
+ self._query_params['include_fallback'] = 'true'
160
+ return self
161
+
162
+ def include_branch(self) -> "TermQuery":
163
+ """Includes the _branch field in the response.
164
+ :return: TermQuery, so you can chain this call.
165
+ """
166
+ self._query_params['include_branch'] = 'true'
167
+ return self
168
+
169
+ def include_count(self) -> "TermQuery":
170
+ """Includes the total count of terms in the response.
171
+ :return: TermQuery, so you can chain this call.
172
+ """
173
+ self._query_params['include_count'] = 'true'
174
+ return self
175
+
176
+ def param(self, key: str, value) -> "TermQuery":
177
+ """Adds an arbitrary query parameter.
178
+ :param key: {str} -- query parameter key
179
+ :param value: value for the query parameter
180
+ :return: TermQuery, so you can chain this call.
181
+ """
182
+ if None in (key, value):
183
+ raise KeyError(ErrorMessages.INVALID_KEY_VALUE_ARGS)
184
+ self._query_params[key] = value
185
+ return self
186
+
187
+ def find(self) -> dict:
188
+ """
189
+ Fetches all terms in the taxonomy.
190
+ GET /taxonomies/{taxonomy_uid}/terms
191
+ :return: dict -- {'terms': [...], 'count'?: n}
192
+ """
193
+ query_str = Utils.do_url_encode(self._query_params)
194
+ return self.http_instance.get(f'{self._url}?{query_str}' if query_str else self._url)