Contentstack 2.2.0__tar.gz → 2.3.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.
- {contentstack-2.2.0 → contentstack-2.3.0}/Contentstack.egg-info/PKG-INFO +1 -1
- {contentstack-2.2.0 → contentstack-2.3.0}/Contentstack.egg-info/SOURCES.txt +3 -1
- {contentstack-2.2.0 → contentstack-2.3.0}/PKG-INFO +1 -1
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/__init__.py +1 -1
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/stack.py +9 -0
- contentstack-2.3.0/contentstack/taxonomy.py +64 -0
- contentstack-2.3.0/tests/test_taxonomies.py +99 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/Contentstack.egg-info/dependency_links.txt +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/Contentstack.egg-info/not-zip-safe +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/Contentstack.egg-info/requires.txt +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/Contentstack.egg-info/top_level.txt +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/LICENSE +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/README.md +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/asset.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/assetquery.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/basequery.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/contenttype.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/controller.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/deep_merge_lp.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/entry.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/entryqueryable.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/globalfields.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/https_connection.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/image_transform.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/query.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/utility.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/contentstack/variants.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/setup.cfg +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/setup.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_assets.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_early_fetch.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_early_find.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_entry.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_global_fields.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_live_preview.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_query.py +0 -0
- {contentstack-2.2.0 → contentstack-2.3.0}/tests/test_stack.py +0 -0
|
@@ -21,6 +21,7 @@ contentstack/https_connection.py
|
|
|
21
21
|
contentstack/image_transform.py
|
|
22
22
|
contentstack/query.py
|
|
23
23
|
contentstack/stack.py
|
|
24
|
+
contentstack/taxonomy.py
|
|
24
25
|
contentstack/utility.py
|
|
25
26
|
contentstack/variants.py
|
|
26
27
|
tests/test_assets.py
|
|
@@ -30,4 +31,5 @@ tests/test_entry.py
|
|
|
30
31
|
tests/test_global_fields.py
|
|
31
32
|
tests/test_live_preview.py
|
|
32
33
|
tests/test_query.py
|
|
33
|
-
tests/test_stack.py
|
|
34
|
+
tests/test_stack.py
|
|
35
|
+
tests/test_taxonomies.py
|
|
@@ -22,7 +22,7 @@ __all__ = (
|
|
|
22
22
|
__title__ = 'contentstack-delivery-python'
|
|
23
23
|
__author__ = 'contentstack'
|
|
24
24
|
__status__ = 'debug'
|
|
25
|
-
__version__ = 'v2.
|
|
25
|
+
__version__ = 'v2.3.0'
|
|
26
26
|
__endpoint__ = 'cdn.contentstack.io'
|
|
27
27
|
__email__ = 'support@contentstack.com'
|
|
28
28
|
__developer_email__ = 'mobile@contentstack.com'
|
|
@@ -6,6 +6,7 @@ from urllib3.util import Retry
|
|
|
6
6
|
from contentstack.asset import Asset
|
|
7
7
|
from contentstack.assetquery import AssetQuery
|
|
8
8
|
from contentstack.contenttype import ContentType
|
|
9
|
+
from contentstack.taxonomy import Taxonomy
|
|
9
10
|
from contentstack.globalfields import GlobalField
|
|
10
11
|
from contentstack.https_connection import HTTPSConnection
|
|
11
12
|
from contentstack.image_transform import ImageTransform
|
|
@@ -204,6 +205,14 @@ class Stack:
|
|
|
204
205
|
"""
|
|
205
206
|
return ContentType(self.http_instance, content_type_uid)
|
|
206
207
|
|
|
208
|
+
def taxonomy(self):
|
|
209
|
+
"""
|
|
210
|
+
taxonomy defines the structure or schema of a page or a section
|
|
211
|
+
of your web or mobile property.
|
|
212
|
+
:return: taxonomy
|
|
213
|
+
"""
|
|
214
|
+
return Taxonomy(self.http_instance)
|
|
215
|
+
|
|
207
216
|
def global_field(self, global_field_uid=None):
|
|
208
217
|
"""
|
|
209
218
|
Global field defines the structure or schema of a page or a section
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from urllib import parse
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Taxonomy:
|
|
8
|
+
def __init__(self, http_instance):
|
|
9
|
+
self.http_instance = http_instance
|
|
10
|
+
self._filters: dict = {}
|
|
11
|
+
|
|
12
|
+
def _add(self, field: str, condition: dict) -> "TaxonomyQuery":
|
|
13
|
+
self._filters[field] = condition
|
|
14
|
+
return self
|
|
15
|
+
|
|
16
|
+
def in_(self, field: str, terms: list) -> "TaxonomyQuery":
|
|
17
|
+
return self._add(field, {"$in": terms})
|
|
18
|
+
|
|
19
|
+
def or_(self, *conds: dict) -> "TaxonomyQuery":
|
|
20
|
+
return self._add("$or", list(conds))
|
|
21
|
+
|
|
22
|
+
def and_(self, *conds: dict) -> "TaxonomyQuery":
|
|
23
|
+
return self._add("$and", list(conds))
|
|
24
|
+
|
|
25
|
+
def exists(self, field: str) -> "TaxonomyQuery":
|
|
26
|
+
return self._add(field, {"$exists": True})
|
|
27
|
+
|
|
28
|
+
def equal_and_below(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyQuery":
|
|
29
|
+
cond = {"$eq_below": term_uid, "levels": levels}
|
|
30
|
+
return self._add(field, cond)
|
|
31
|
+
|
|
32
|
+
def below(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyQuery":
|
|
33
|
+
cond = {"$below": term_uid, "levels": levels}
|
|
34
|
+
return self._add(field, cond)
|
|
35
|
+
|
|
36
|
+
def equal_and_above(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyQuery":
|
|
37
|
+
cond = {"$eq_above": term_uid, "levels": levels}
|
|
38
|
+
return self._add(field, cond)
|
|
39
|
+
|
|
40
|
+
def above(self, field: str, term_uid: str, levels: int = 10) -> "TaxonomyQuery":
|
|
41
|
+
cond = {"$above": term_uid, "levels": levels}
|
|
42
|
+
return self._add(field, cond)
|
|
43
|
+
|
|
44
|
+
def find(self, params=None):
|
|
45
|
+
"""
|
|
46
|
+
This method fetches entries filtered by taxonomy from the stack.
|
|
47
|
+
"""
|
|
48
|
+
self.local_param = {}
|
|
49
|
+
self.local_param['environment'] = self.http_instance.headers['environment']
|
|
50
|
+
|
|
51
|
+
# Ensure query param is always present
|
|
52
|
+
query_string = json.dumps(self._filters or {})
|
|
53
|
+
query_encoded = quote(query_string, safe='{}":,[]') # preserves JSON characters
|
|
54
|
+
|
|
55
|
+
# Build the base URL
|
|
56
|
+
endpoint = self.http_instance.endpoint
|
|
57
|
+
url = f'{endpoint}/taxonomies/entries?environment={self.local_param["environment"]}&query={query_encoded}'
|
|
58
|
+
|
|
59
|
+
# Append any additional params manually
|
|
60
|
+
if params:
|
|
61
|
+
other_params = '&'.join(f'{k}={v}' for k, v in params.items())
|
|
62
|
+
url += f'&{other_params}'
|
|
63
|
+
return self.http_instance.get(url)
|
|
64
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import unittest
|
|
3
|
+
import config
|
|
4
|
+
import contentstack
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
API_KEY = config.APIKEY
|
|
8
|
+
DELIVERY_TOKEN = config.DELIVERYTOKEN
|
|
9
|
+
ENVIRONMENT = config.ENVIRONMENT
|
|
10
|
+
HOST = config.HOST
|
|
11
|
+
|
|
12
|
+
class TestTaxonomyAPI(unittest.TestCase):
|
|
13
|
+
def setUp(self):
|
|
14
|
+
self.stack = contentstack.Stack(API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=HOST)
|
|
15
|
+
|
|
16
|
+
def test_01_taxonomy_complex_query(self):
|
|
17
|
+
"""Test complex taxonomy query combining multiple filters"""
|
|
18
|
+
taxonomy = self.stack.taxonomy()
|
|
19
|
+
result = taxonomy.and_(
|
|
20
|
+
{"taxonomies.category": {"$in": ["test"]}},
|
|
21
|
+
{"taxonomies.test1": {"$exists": True}}
|
|
22
|
+
).or_(
|
|
23
|
+
{"taxonomies.status": {"$in": ["active"]}},
|
|
24
|
+
{"taxonomies.priority": {"$in": ["high"]}}
|
|
25
|
+
).find({'limit': 10})
|
|
26
|
+
if result is not None:
|
|
27
|
+
self.assertIn('entries', result)
|
|
28
|
+
|
|
29
|
+
def test_02_taxonomy_in_query(self):
|
|
30
|
+
"""Test taxonomy query with $in filter"""
|
|
31
|
+
taxonomy = self.stack.taxonomy()
|
|
32
|
+
result = taxonomy.in_("taxonomies.category", ["category1", "category2"]).find()
|
|
33
|
+
if result is not None:
|
|
34
|
+
self.assertIn('entries', result)
|
|
35
|
+
|
|
36
|
+
def test_03_taxonomy_exists_query(self):
|
|
37
|
+
"""Test taxonomy query with $exists filter"""
|
|
38
|
+
taxonomy = self.stack.taxonomy()
|
|
39
|
+
result = taxonomy.exists("taxonomies.test1").find()
|
|
40
|
+
if result is not None:
|
|
41
|
+
self.assertIn('entries', result)
|
|
42
|
+
|
|
43
|
+
def test_04_taxonomy_or_query(self):
|
|
44
|
+
"""Test taxonomy query with $or filter"""
|
|
45
|
+
taxonomy = self.stack.taxonomy()
|
|
46
|
+
result = taxonomy.or_(
|
|
47
|
+
{"taxonomies.category": {"$in": ["category1"]}},
|
|
48
|
+
{"taxonomies.test1": {"$exists": True}}
|
|
49
|
+
).find()
|
|
50
|
+
if result is not None:
|
|
51
|
+
self.assertIn('entries', result)
|
|
52
|
+
|
|
53
|
+
def test_05_taxonomy_and_query(self):
|
|
54
|
+
"""Test taxonomy query with $and filter"""
|
|
55
|
+
taxonomy = self.stack.taxonomy()
|
|
56
|
+
result = taxonomy.and_(
|
|
57
|
+
{"taxonomies.category": {"$in": ["category1"]}},
|
|
58
|
+
{"taxonomies.test1": {"$exists": True}}
|
|
59
|
+
).find()
|
|
60
|
+
if result is not None:
|
|
61
|
+
self.assertIn('entries', result)
|
|
62
|
+
|
|
63
|
+
def test_06_taxonomy_equal_and_below(self):
|
|
64
|
+
"""Test taxonomy query with $eq_below filter"""
|
|
65
|
+
taxonomy = self.stack.taxonomy()
|
|
66
|
+
result = taxonomy.equal_and_below("taxonomies.color", "blue", levels=1).find()
|
|
67
|
+
if result is not None:
|
|
68
|
+
self.assertIn('entries', result)
|
|
69
|
+
|
|
70
|
+
def test_07_taxonomy_below(self):
|
|
71
|
+
"""Test taxonomy query with $below filter"""
|
|
72
|
+
taxonomy = self.stack.taxonomy()
|
|
73
|
+
result = taxonomy.below("taxonomies.hierarchy", "parent_uid", levels=2).find()
|
|
74
|
+
if result is not None:
|
|
75
|
+
self.assertIn('entries', result)
|
|
76
|
+
|
|
77
|
+
def test_08_taxonomy_equal_and_above(self):
|
|
78
|
+
"""Test taxonomy query with $eq_above filter"""
|
|
79
|
+
taxonomy = self.stack.taxonomy()
|
|
80
|
+
result = taxonomy.equal_and_above("taxonomies.hierarchy", "child_uid", levels=3).find()
|
|
81
|
+
if result is not None:
|
|
82
|
+
self.assertIn('entries', result)
|
|
83
|
+
|
|
84
|
+
def test_09_taxonomy_above(self):
|
|
85
|
+
"""Test taxonomy query with $above filter"""
|
|
86
|
+
taxonomy = self.stack.taxonomy()
|
|
87
|
+
result = taxonomy.above("taxonomies.hierarchy", "child_uid", levels=2).find()
|
|
88
|
+
if result is not None:
|
|
89
|
+
self.assertIn('entries', result)
|
|
90
|
+
|
|
91
|
+
def test_10_taxonomy_find_with_params(self):
|
|
92
|
+
"""Test taxonomy find with additional parameters"""
|
|
93
|
+
taxonomy = self.stack.taxonomy()
|
|
94
|
+
result = taxonomy.in_("taxonomies.category", ["test"]).find({'limit': 5})
|
|
95
|
+
if result is not None:
|
|
96
|
+
self.assertIn('entries', result)
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|