11import json
2- from urllib import parse
32from urllib .parse import quote
43
4+ from contentstack .error_messages import ErrorMessages
5+ from contentstack .utility import Utils
56
67
7- class Taxonomy :
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+
816 def __init__ (self , http_instance ):
917 self .http_instance = http_instance
1018 self ._filters : dict = {}
1119
12- def _add (self , field : str , condition : dict ) -> "TaxonomyQuery " :
20+ def _add (self , field : str , condition : dict ) -> "TaxonomyFilter " :
1321 self ._filters [field ] = condition
1422 return self
1523
16- def in_ (self , field : str , terms : list ) -> "TaxonomyQuery " :
24+ def in_ (self , field : str , terms : list ) -> "TaxonomyFilter " :
1725 return self ._add (field , {"$in" : terms })
1826
19- def or_ (self , * conds : dict ) -> "TaxonomyQuery " :
27+ def or_ (self , * conds : dict ) -> "TaxonomyFilter " :
2028 return self ._add ("$or" , list (conds ))
2129
22- def and_ (self , * conds : dict ) -> "TaxonomyQuery " :
30+ def and_ (self , * conds : dict ) -> "TaxonomyFilter " :
2331 return self ._add ("$and" , list (conds ))
2432
25- def exists (self , field : str ) -> "TaxonomyQuery " :
33+ def exists (self , field : str ) -> "TaxonomyFilter " :
2634 return self ._add (field , {"$exists" : True })
2735
28- def equal_and_below (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyQuery " :
36+ def equal_and_below (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyFilter " :
2937 cond = {"$eq_below" : term_uid , "levels" : levels }
3038 return self ._add (field , cond )
3139
32- def below (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyQuery " :
40+ def below (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyFilter " :
3341 cond = {"$below" : term_uid , "levels" : levels }
3442 return self ._add (field , cond )
3543
36- def equal_and_above (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyQuery " :
44+ def equal_and_above (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyFilter " :
3745 cond = {"$eq_above" : term_uid , "levels" : levels }
3846 return self ._add (field , cond )
3947
40- def above (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyQuery " :
48+ def above (self , field : str , term_uid : str , levels : int = 10 ) -> "TaxonomyFilter " :
4149 cond = {"$above" : term_uid , "levels" : levels }
4250 return self ._add (field , cond )
4351
@@ -62,3 +70,164 @@ def find(self, params=None):
6270 url += f'&{ other_params } '
6371 return self .http_instance .get (url )
6472
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 commit comments