From ea03b4cd2960d39dca37dab69ab10588505728bd Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Wed, 7 Oct 2020 20:56:31 +0530 Subject: [PATCH 1/8] include_fallback added --- CHANGELOG.md | 32 ++++++++++-- changelog.rst | 49 +++++++++++++----- contentstack/__init__.py | 2 +- contentstack/asset.py | 18 +++++++ contentstack/assetquery.py | 16 ++++++ contentstack/entry.py | 19 +++++++ contentstack/entryqueryable.py | 4 +- contentstack/https_connection.py | 16 +++++- contentstack/query.py | 86 +++++++------------------------- contentstack/stack.py | 4 +- contentstack/utility.py | 3 +- requirements.txt | 73 ++------------------------- tests/test_assets.py | 12 +++++ tests/test_entry.py | 5 ++ tests/test_query.py | 10 ++++ 15 files changed, 188 insertions(+), 161 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3302c..147b814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,44 @@ # CHANGELOG -Date: 10-Aug-2020 - include_reference issue fixed + +*v1.2.0* +============ + +_Date: 20-Oct-2020_ + + - include_fallback Support Added + - Timeout support included + +- Entry + - added support for include_fallback. +- Asset + - added support for include_fallback. +- AssetQueryable + - added support for include_fallback. +- Query + - added support for include_fallback. + +----------------------------- + + + ## _v1.1.0_ +_Date: 10-Aug-2020 - include_reference issue fixed_ + EntryQueryable - updated include_reference function. ----------------------------- -Date: 17-Jun-2020 - initial release + ## _v1.0.0_ +_Date: 17-Jun-2020 - initial release_ + Stack - Initialisation of stack has been modified @@ -34,11 +59,10 @@ Query ----------------------------- -Date: 18-Nov-2019 - beta release ## _v0.1.0_ -November-18, 2019 -beta release +_November-18, 2019 -beta release_ Initial release for the contentstack-python-sdk for Content Delivery API diff --git a/changelog.rst b/changelog.rst index eebe69c..66f6c0c 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,39 +1,64 @@ -========= -CHANGELOG -========= +================ +**CHANGELOG** +================ + +*v1.2.0* +============ + +**Date: 20-Oct-2020** + + - include_fallback Support Added + - Timeout support included + + +**Entry** + - Added support for include_fallback. +**Asset** + - Added support for include_fallback. +**AssetQuery** + - Added support for include_fallback. +**Query** + - Added support for include_fallback. + +============ + -Date: 10-Aug-2020 - include_reference issue fixed *v1.1.0* ============ -EntryQueryable - - updated include_reference function. +**Date: 10-Aug-2020 :: include_reference issue fixed** + +**EntryQueryable** + - updated include_reference function. ============ -Date: 17-Jun-2020 - initial release *v1.0.0* ============ -Stack +**Date: 17-Jun-2020 :: Initial Release** + +**Stack** - Initialization of the stack has been modified - External config support moved to stack initialization optional parameters -Asset +**Asset** - changes incorporated in Asset class. -Entry +**Entry** - changes incorporated in the entry class. -Query +**Query** - Changes incorporated in the Query class. ----------------------------- -Date: 18-Nov-2019 - beta release *v0.0.1* ============ + +**Date: 18-Nov-2019 :: Beta Release** + - Beta release for the contentstack-python SDK for Content Delivery API \ No newline at end of file diff --git a/contentstack/__init__.py b/contentstack/__init__.py index 99d6a72..2aa018b 100644 --- a/contentstack/__init__.py +++ b/contentstack/__init__.py @@ -19,6 +19,6 @@ __title__ = 'contentstack-python' __author__ = 'Contentstack' __status__ = 'debug' -__version__ = '1.1.0' +__version__ = '1.2.0' __endpoint__ = 'cdn.contentstack.io' __email__ = 'shailesh.mishra@contentstack.com' diff --git a/contentstack/asset.py b/contentstack/asset.py index db568ae..a30c368 100644 --- a/contentstack/asset.py +++ b/contentstack/asset.py @@ -121,6 +121,24 @@ def include_dimension(self): """ self.__query_params['include_dimension'] = "true" return self + + + def include_fallback(self): + r"""Include the fallback locale publish content, if specified locale content is not publish. + + :return: Asset, so we can chain the call + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> asset = stack.asset(uid='asset_uid') + >>> asset = asset.include_fallback() + ---------------------------- + """ + self.__query_params['include_fallback'] = "true" + return self def fetch(self): r"""This call fetches the latest version of a specific asset of a particular stack. diff --git a/contentstack/assetquery.py b/contentstack/assetquery.py index fb5202d..e0cf161 100644 --- a/contentstack/assetquery.py +++ b/contentstack/assetquery.py @@ -97,6 +97,22 @@ def relative_url(self): """ self.__query_params["relative_urls"] = "true" return self + + def include_fallback(self): + r"""Include the fallback locale publish content, if specified locale content is not publish. + + :return: AssetQuery, so we can chain the call + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> result = stack.asset_query().include_fallback().find() + ---------------------------- + """ + self.__query_params['include_fallback'] = "true" + return self def find(self): r"""This call fetches the list of all the assets of a particular stack. diff --git a/contentstack/entry.py b/contentstack/entry.py index fffc2a7..9b107bb 100644 --- a/contentstack/entry.py +++ b/contentstack/entry.py @@ -98,6 +98,25 @@ def param(self, key: str, value: any): raise ValueError('Kindly provide valid key and value arguments') self.entry_param[key] = value return self + + def include_fallback(self): + r"""Include the fallback locale publish content, if specified locale content is not publish. + + :return: Entry, so we can chain the call + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> content_type = stack.content_type('content_type_uid') + >>> entry = content_type.entry(uid='entry_uid') + >>> entry = entry.include_fallback() + >>> result = entry.fetch() + ---------------------------- + """ + self.entry_param['include_fallback'] = "true" + return self def __get_base_url(self): if None in (self.http_instance, self.content_type_id, self.entry_uid): diff --git a/contentstack/entryqueryable.py b/contentstack/entryqueryable.py index b26c4c5..8368c26 100644 --- a/contentstack/entryqueryable.py +++ b/contentstack/entryqueryable.py @@ -73,7 +73,7 @@ def excepts(self, field_uid: str): def include_reference(self, field_uid): """ - **Include Reference:** + **[Include Reference]:** When you fetch an entry of a content type that has a reference field, by default, the content of the referred entry is not fetched. It only fetches the UID of the referred entry, along with the content of @@ -112,9 +112,7 @@ def include_content_type(self): >>> entry = content_type.entry('uid') >>> entry.include_content_type() >>> result = entry.fetch() - ------------------------------- - [Example: for Query:] >>> import contentstack diff --git a/contentstack/https_connection.py b/contentstack/https_connection.py index 5322fdf..91040cf 100644 --- a/contentstack/https_connection.py +++ b/contentstack/https_connection.py @@ -10,9 +10,11 @@ import requests from requests.exceptions import Timeout, HTTPError - import contentstack +# from requests.adapters import HTTPAdapter +# from requests.packages.urllib3.util.retry import Retry + def get_os_platform(): """ returns client platform """ @@ -52,9 +54,19 @@ def get(self, url): We use requests.get method since we are sending a GET request. The four arguments we pass are url, verify(ssl), timeout, headers """ + + # retry_strategy = Retry( + # total=3, + # status_forcelist=[429, 500, 502, 503, 504], + # method_whitelist=["GET"] + # ) + # adapter = HTTPAdapter(max_retries=retry_strategy) + # http = requests.Session() + # http.mount("https://", adapter) + try: self.headers.update(user_agents()) - response = requests.get(url, verify=True, headers=self.headers) + response = requests.get(url, verify=True, timeout=(10, 8), headers=self.headers) response.encoding = 'utf-8' return response.json() except Timeout: diff --git a/contentstack/query.py b/contentstack/query.py index 242829c..df8c863 100644 --- a/contentstack/query.py +++ b/contentstack/query.py @@ -80,71 +80,6 @@ def query_operator(self, query_type: QueryType, *query_objects): self.query_params["query"] = json.dumps({query_type.value: __container}) return self - # def and_query(self, *query_objects): - # """ - # Get entries that satisfy all the conditions provided in the '$and' query. - # Arguments: - # query_objects {Query} -- query_objects for variable number - # of arguments of type Query Object. - # Raises: - # ValueError: If query_objects is None - # Returns: - # Query -- Query object, so you can chain this call. - # --------------------------------- - # [Example]: - # >>> import contentstack - # >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') - # >>> query = stack.content_type('content_type_uid').query() - # >>> query_one = query.where('field_uid', - # QueryOperation.EQUALS, fields=['field1', 'field2', 'field3']) - # >>> query_two = query.where('field_uid', - # QueryOperation.EQUALS, fields=['field1', 'field2', 'field3']) - # >>> result = query.and_query(query_one, query_two).find() - # --------------------------------- - # """ - # __container = [] - # if len(query_objects) > 0: - # for query in query_objects: - # __container.append(query.parameters) - # self.query_params["query"] = json.dumps({"$and": __container}) - # if len(self.parameters) > 0: - # self.parameters.clear() - # return self - # - # def or_query(self, *query_objects): - # """ - # Get all entries that satisfy at least - # one of the given conditions provided in the '$or' query. - # Arguments: - # query_objects {object} -- query_objects for variable - # number of arguments of type Query Object. - # Raises: - # ValueError: If query_objects is None - # Returns: - # Query -- Query object, so you can chain this call. - # ---------------------------------- - # [Example]: - # - # >>> import contentstack - # >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') - # >>> query = stack.content_type('content_type_uid').query() - # >>> query_one = query.where('field_uid', - # QueryOperation.EQUALS, fields=['field1', 'field2', 'field3']) - # >>> query_two = query.where('field_uid', - # QueryOperation.EQUALS, fields=['field1', 'field2', 'field3']) - # >>> result = query.or_query(query_one, query_two).find() - # ---------------------------------- - # """ - # __container = [] - # if len(query_objects) > 0: - # for i in range(len(query_objects)): - # obj = query_objects[i].parameters[list(query_objects[i].parameters)[i]] - # __container.append(obj) - # self.query_params["query"] = json.dumps({"$or": __container}) - # if len(self.parameters) > 0: - # self.parameters.clear() - # return self - def tags(self, *tags): """ Include tags with which to search entries accepts variable-length argument lists @@ -249,6 +184,25 @@ def where_not_in(self, key, query_object): else: raise ValueError('Invalid Key or Value provided') return self + + def include_fallback(self): + r"""Include the fallback locale publish content, if specified locale content is not publish. + + :return: Query, so we can chain the call + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> content_type = stack.content_type('content_type_uid') + >>> query = content_type.query() + >>> query = query.include_fallback() + >>> result = query.find() + ---------------------------- + """ + self.query_params['include_fallback'] = "true" + return self def find(self): """It fetches the query result. @@ -267,8 +221,6 @@ def find(self): >>> result = query.find() ------------------------------------- """ - # if len(self.entry_queryable_param) > 0: - # self.query_params.update(self.entry_queryable_param) return self.__execute_network_call() def find_one(self): diff --git a/contentstack/stack.py b/contentstack/stack.py index 39d1c2a..5cc6f77 100644 --- a/contentstack/stack.py +++ b/contentstack/stack.py @@ -151,8 +151,8 @@ def asset_query(self): def sync_init(self, content_type_uid=None, from_date=None, locale=None, publish_type=None): """ - Constructs and initialises sync if no params provided else - below mentioned params can be provided to get the response accordingly + Constructs and initialises sync if no params provided else below mentioned params + can be provided to get the response accordingly :param content_type_uid: subsequent syncs will only include the entries of the specified content_type. :param from_date: use from_date and specify the start date as its value. diff --git a/contentstack/utility.py b/contentstack/utility.py index ae3ef68..9860bb6 100644 --- a/contentstack/utility.py +++ b/contentstack/utility.py @@ -63,7 +63,8 @@ def do_url_encode(params): @staticmethod def get_complete_url(base_url: str, params: dict): - """ creates complete url using base_url and their respective parameters + """ + creates complete url using base_url and their respective parameters :param base_url: :param params: :return: diff --git a/requirements.txt b/requirements.txt index bdf8532..1385fd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,72 +1,7 @@ -aenum==2.2.3 -alabaster==0.7.12 -astroid==2.4.1 -attrs==19.3.0 -Babel==2.8.0 -bleach==3.1.5 -certifi==2020.6.20 -chardet==3.0.4 -click==7.1.2 -coverage==5.2.1 -coverage-badge==1.0.1 -dnspython==2.0.0 docutils==0.16 -dparse==0.5.1 -entrypoints==0.3 -enum34==1.1.10 -env==0.1.0 -eventlet==0.25.2 -greenlet==0.4.15 -html-testRunner==1.2.1 -idna==2.10 -imagesize==1.2.0 -importlib-metadata==0.23 -iniconfig==1.0.0 -isort==4.3.21 -itsdangerous==1.1.0 -Jinja2==3.0.0a1 -keyring==21.3.0 -lazy-object-proxy==1.4.3 -MarkupSafe==2.0.0a1 -mccabe==0.6.1 -monotonic==1.5 -more-itertools==7.2.0 -packaging==20.4 -pip-autoremove==0.9.1 -pipdeptree==1.0.0 -piplint==0.2.0 +keyring==21.4.0 pkginfo==1.5.0.1 -pluggy==0.13.1 -py==1.9.0 -Pygments==2.6.1 -pylint==2.5.2 -pyparsing==3.0.0a2 -pytest==6.0.1 -pytest-cov==2.9.0 -pytz==2020.1 -PyYAML==5.3.1 -readme-renderer==24.0 requests==2.24.0 -requests-toolbelt==0.9.1 -safety==1.9.0 -six==1.15.0 -snowballstemmer==2.0.0 -Sphinx==3.1.2 -sphinx-rtd-theme==0.4.3 -sphinxcontrib-applehelp==1.0.2 -sphinxcontrib-devhelp==1.0.2 -sphinxcontrib-htmlhelp==1.0.3 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.3 -sphinxcontrib-serializinghtml==1.1.4 -toml==0.10.1 -tqdm==4.38.0 -twine==3.1.1 -typed-ast==1.4.1 -urllib3==1.25.10 -virtualenv==16.7.9 -wcwidth==0.2.5 -webencodings==0.5.1 -Werkzeug==1.0.1 -wrapt==1.11.2 -zipp==3.1.0 +pip~=20.2.3 +py~=1.9.0 +setuptools~=50.3.0 \ No newline at end of file diff --git a/tests/test_assets.py b/tests/test_assets.py index 1bd9759..ed6b8ee 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -6,6 +6,7 @@ from contentstack.basequery import QueryOperation from tests import credentials +# global asset_uid global asset_uid @@ -64,6 +65,12 @@ def test_18_add_param(self): self.asset.params("paramKey", 'paramValue') print(self.asset.base_url) + def test_19_support_include_fallback(self): + global asset_uid + self.asset = self.stack.asset(uid=asset_uid) + self.asset.include_fallback() + print(self.asset.base_url) + ############################################ # ==== Asset Query ==== ############################################ @@ -126,6 +133,11 @@ def test_15_environment(self): query = self.asset_query.environment("dev") self.assertEqual('dev', query.http_instance.headers['environment']) + def test_16_support_include_fallback(self): + query = self.asset_query.include_fallback() + result = query.find() + self.assertEqual(True, result) + suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) diff --git a/tests/test_entry.py b/tests/test_entry.py index 7d793ff..b7eb842 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -90,6 +90,11 @@ def test_12_entry_include_reference_github_issue(self): ["categories", "brand"]) response = github_entry.fetch() + + def test_13_entry_support_include_fallback(self): + global entry_uid + entry = self.stack.content_type('faq').entry(entry_uid).include_fallback() + self.assertEqual(True, entry.entry_param.__contains__('include_fallback')) suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) diff --git a/tests/test_query.py b/tests/test_query.py index b074718..c8691df 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -114,6 +114,16 @@ def test_16_base_query(self): def test_17_base_remove_param(self): query = self.query3.remove_param("keyOne") logging.info(query.base_url) + + def test_18_support_include_fallback(self): + query = self.query3.include_fallback() + logging.info(query.base_url) + self.assertEqual('true', query.query_params['include_fallback']) + + def test_18_support_include_fallback_url(self): + query = self.query3.include_fallback() + logging.info(query.base_url) + self.assertEqual({'include_fallback': 'true'}, query.query_params) suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) From 86dd1a05863b118208ebaa56d7628a3494ce232c Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Wed, 7 Oct 2020 20:58:49 +0530 Subject: [PATCH 2/8] include_fallback added --- README.rst | 69 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/README.rst b/README.rst index 42ca26c..62edcf4 100644 --- a/README.rst +++ b/README.rst @@ -5,24 +5,24 @@ Python SDK for Contentstack =========================== -Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. `Read More `_. + Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. `Read More `_. -Contentstack provides Python SDK to build an application on top of Python. Given below is the detailed guide and helpful resources to get started with our Python SDK. + Contentstack provides Python SDK to build an application on top of Python. Given below is the detailed guide and helpful resources to get started with our Python SDK. Prerequisite ============ -You will need python 3 installed on your machine. You can install it from `here `_ -. + You will need python 3 installed on your machine. You can install it from `here `_ Setup and Installation ====================== -To use the Contentstack Python SDK to your existing project, perform the steps given below: + To use the Contentstack Python SDK to your existing project, perform the steps given below: -**Install contentstack pip** +.. code-block:: python + + Install contentstack pip - ``pip install contentstack`` This is the preferred method to install contentstack, as it will always install the most recent stable release. If you don't have `pip `_ installed, this `Python installation guide `_ can guide you through the process @@ -33,11 +33,11 @@ Key Concepts for using Contentstack **Stack** - A stack is like a container that holds the content of your app. Learn more about `Stacks `_. + A stack is like a container that holds the content of your app. Learn more about `Stacks `_. -**Content Type** +**Content-Type** - Content type lets you define the structure or blueprint of a page or a section of your digital property. It is a form-like page that gives Content Managers an interface to input and upload content. `read_more `_. + Content-type lets you define the structure or blueprint of a page or a section of your digital property. It is a form-like page that gives Content Managers an interface to input and upload content. `read_more `_. **Entry** @@ -58,37 +58,39 @@ Contentstack Python SDK: 5-minute Quickstart **Initializing your SDK** - To initialize the SDK, specify application API key, access token, and environment name of the stack as shown in the snippet given below: + To initialize the SDK, specify the application API key, access token, and environment name of the stack as shown in the snippet given below: - ```stack = contentstack.Stack('api_key', 'access_token', 'environment')``` +.. code-block:: python - To get the API credentials mentioned above, log in to your Contentstack account and then in your top panel navigation, go to Settings > Stack to view the API Key and Access Token. + stack = contentstack.Stack('api_key', 'access_token', 'environment') +To get the API credentials mentioned above, log in to your Contentstack account and then in your top panel navigation, go to Settings > Stack to view the API Key and Access Token. -**Querying content from your stack** - To retrieve a single entry from a content type use the code snippet given below: +**Querying content from your stack** - ```content_type = stack.content_type("content_type_uid")``` +To retrieve a single entry from a content type use the code snippet given below: - ```entry = content_type.entry("entry_uid")``` +.. code-block:: python - ```result = entry.fetch()``` + content_type = stack.content_type("content_type_uid") + entry = content_type.entry("entry_uid") + result = entry.fetch() **Get Multiple Entries** - To retrieve multiple entries of a particular content type, use the code snippet given below: +To retrieve multiple entries of a particular content type, use the code snippet given below: **stack is an instance of Stack class** - ```query = stack.content_type("content_type_uid").query()``` - - ```result = query.find()``` +.. code-block:: python + query = stack.content_type("content_type_uid").query() + result = query.find() **Advanced Queries** @@ -101,17 +103,22 @@ Contentstack Python SDK: 5-minute Quickstart *For example:* - if you want to crop an image (with width as 300 and height as 400), you simply need to append query parameters at the end of the image URL, such as ```https://images.contentstack.io/v3/assets/blteae40eb499811073/bltc5064f36b5855343/59e0c41ac0eddd140d5a8e3e/download?crop=300,400``` + if you want to crop an image (with a width of 300 and height of 400), you simply need to append query parameters at the end of the image URL, such as + +.. code-block:: python + + https://images.contentstack.io/v3/assets/blteae40eb499811073/bltc5064f36b5855343/59e0c41ac0eddd140d5a8e3e/download?crop=300,400 - There are several more parameters that you can use for your images. `Read Image Delivery API documentation `_ - You can use the Image Delivery API functions in this SDK as well. Here are a few examples of its usage in the SDK. +There are several more parameters that you can use for your images. `Read Image Delivery API documentation `_ - ```url = stack.image_transform(image_url, {'quality': 100})``` +You can use the Image Delivery API functions in this SDK as well. Here are a few examples of its usage in the SDK. - ```url = stack.image_transform(imageUrl, {'width': 100, 'height': 100})``` +.. code-block:: python - ```url = stack.image_transform(imageUrl, {'auto': 'webp'})``` + url = stack.image_transform(image_url, {'quality': 100}) + url = stack.image_transform(imageUrl, {'width': 100, 'height': 100}) + url = stack.image_transform(imageUrl, {'auto': 'webp'}) **Using the Sync API with Python SDK** @@ -122,11 +129,11 @@ Read through to understand how to use the Sync API with Contentstack Python SDK. **Helpful Links** -`Contentstack Website `_ + `Contentstack Website `_ -`Official Documentation `_ + `Official Documentation `_ -`Content Delivery API Docs `_. + `Content Delivery API Docs `_. The MIT License (MIT) From 70a07f8346fdd5ed71054c017131245b1bca36d2 Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Thu, 8 Oct 2020 15:04:27 +0530 Subject: [PATCH 3/8] timeout code_coverage --- contentstack/asset.py | 17 ++++++------- contentstack/assetquery.py | 14 +++++------ coverage.svg | 4 +-- tests/__init__.py | 1 + tests/test_assets.py | 51 +++++++++++++++++++++++--------------- tests/test_entry.py | 18 ++++++++++++-- tests/test_query.py | 13 ++++++++++ 7 files changed, 78 insertions(+), 40 deletions(-) diff --git a/contentstack/asset.py b/contentstack/asset.py index a30c368..76ca881 100644 --- a/contentstack/asset.py +++ b/contentstack/asset.py @@ -15,13 +15,13 @@ class Asset: def __init__(self, http_instance, uid=None): self.http_instance = http_instance - self.__query_params = {} + self.asset_params = {} self.__uid = uid if self.__uid is None or self.__uid.strip() == 0: raise KeyError('Please provide valid uid') self.base_url = '{}/assets/{}'.format(self.http_instance.endpoint, self.__uid) if 'environment' in self.http_instance.headers: - self.__query_params['environment'] = self.http_instance.headers['environment'] + self.asset_params['environment'] = self.http_instance.headers['environment'] # self.http_instance.headers.pop('environment') def environment(self, environment): @@ -84,7 +84,7 @@ def params(self, key, value): """ if None in (key, value) or not isinstance(key, str): raise KeyError('Kindly provide valid params') - self.__query_params[key] = value + self.asset_params[key] = value return self def relative_urls(self): @@ -101,7 +101,7 @@ def relative_urls(self): >>> asset = asset.relative_urls() ---------------------------- """ - self.__query_params['relative_urls'] = 'true' + self.asset_params['relative_urls'] = 'true' return self def include_dimension(self): @@ -119,10 +119,9 @@ def include_dimension(self): >>> asset = asset.include_dimension() ---------------------------- """ - self.__query_params['include_dimension'] = "true" + self.asset_params['include_dimension'] = "true" return self - - + def include_fallback(self): r"""Include the fallback locale publish content, if specified locale content is not publish. @@ -137,7 +136,7 @@ def include_fallback(self): >>> asset = asset.include_fallback() ---------------------------- """ - self.__query_params['include_fallback'] = "true" + self.asset_params['include_fallback'] = "true" return self def fetch(self): @@ -154,5 +153,5 @@ def fetch(self): >>> result = asset.fetch() ------------------------------ """ - url = '{}?{}'.format(self.base_url, parse.urlencode(self.__query_params)) + url = '{}?{}'.format(self.base_url, parse.urlencode(self.asset_params)) return self.http_instance.get(url) diff --git a/contentstack/assetquery.py b/contentstack/assetquery.py index e0cf161..9496170 100644 --- a/contentstack/assetquery.py +++ b/contentstack/assetquery.py @@ -19,7 +19,7 @@ class AssetQuery(BaseQuery): def __init__(self, http_instance): super().__init__() self.http_instance = http_instance - self.__query_params = {} + self.asset_query_params = {} self.base_url = "{}/assets".format(self.http_instance.endpoint) if "environment" in self.http_instance.headers: env = self.http_instance.headers["environment"] @@ -62,7 +62,7 @@ def version(self, version): >>> result = stack.asset_query().version(3).find() ------------------------------ """ - self.__query_params["version"] = version + self.asset_query_params["version"] = version return self def include_dimension(self): @@ -79,7 +79,7 @@ def include_dimension(self): >>> result = stack.asset_query().include_dimension().find() ------------------------------ """ - self.__query_params["include_dimension"] = "true" + self.asset_query_params["include_dimension"] = "true" return self def relative_url(self): @@ -95,7 +95,7 @@ def relative_url(self): >>> result = stack.asset_query().relative_url().find() ------------------------------ """ - self.__query_params["relative_urls"] = "true" + self.asset_query_params["relative_urls"] = "true" return self def include_fallback(self): @@ -111,7 +111,7 @@ def include_fallback(self): >>> result = stack.asset_query().include_fallback().find() ---------------------------- """ - self.__query_params['include_fallback'] = "true" + self.asset_query_params['include_fallback'] = "true" return self def find(self): @@ -131,6 +131,6 @@ def find(self): """ if self.parameters is not None and len(self.parameters) > 0: - self.__query_params["query"] = json.dumps(self.parameters) - url = Utils.get_complete_url(self.base_url, self.__query_params) + self.asset_query_params["query"] = json.dumps(self.parameters) + url = Utils.get_complete_url(self.base_url, self.asset_query_params) return self.http_instance.get(url) diff --git a/coverage.svg b/coverage.svg index 318685c..6963b3e 100644 --- a/coverage.svg +++ b/coverage.svg @@ -15,7 +15,7 @@ coverage coverage - 85% - 85% + 87% + 87% diff --git a/tests/__init__.py b/tests/__init__.py index 76c5479..769ed0d 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,7 @@ # python3 -m unittest tests # clean all the .pyc files # find . -name \*.pyc -delete +# pytest --cov=contentstack import unittest from unittest import TestLoader, TestSuite from HtmlTestRunner import HTMLTestRunner diff --git a/tests/test_assets.py b/tests/test_assets.py index ed6b8ee..84fb09e 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -47,96 +47,107 @@ def test_04_asset_filetype(self): if result is not None: self.assertEqual('image/jpeg', result['asset']['content_type']) - def test_16_remove_environment(self): + def test_05_remove_environment(self): global asset_uid self.asset = self.stack.asset(uid=asset_uid) self.asset.remove_environment() self.assertEqual(False, 'environment' in self.asset.http_instance.headers) - def test_17_add_environment(self): + def test_06_add_environment(self): global asset_uid self.asset = self.stack.asset(uid=asset_uid) self.asset.environment("dev") self.assertEqual('dev', self.asset.http_instance.headers['environment']) - def test_18_add_param(self): + def test_07_add_param(self): global asset_uid self.asset = self.stack.asset(uid=asset_uid) self.asset.params("paramKey", 'paramValue') print(self.asset.base_url) - def test_19_support_include_fallback(self): + def test_08_support_include_fallback(self): global asset_uid self.asset = self.stack.asset(uid=asset_uid) - self.asset.include_fallback() - print(self.asset.base_url) + asset_params = self.asset.include_fallback().asset_params + self.assertEqual({'environment': 'development', 'include_fallback': 'true'}, asset_params) ############################################ # ==== Asset Query ==== ############################################ - def test_05_assets_query(self): + def test_09_assets_query(self): result = self.asset_query.find() if result is not None: self.assertEqual(8, len(result['assets'])) - def test_06_assets_base_query_where_exclude_title(self): + def test_10_assets_base_query_where_exclude_title(self): query = self.asset_query.where('title', QueryOperation.EXCLUDES, fields=['images_(1).jpg']) result = query.find() if result is not None: self.assertEqual(7, len(result['assets'])) - def test_07_assets_base_query_where_equals_str(self): + def test_11_assets_base_query_where_equals_str(self): query = self.asset_query.where('title', QueryOperation.EQUALS, fields='images_(1).jpg') result = query.find() if result is not None: self.assertEqual("images_(1).jpg", result['assets'][0]['filename']) - def test_08_assets_base_query_where_exclude(self): + def test_12_assets_base_query_where_exclude(self): query = self.asset_query.where('file_size', QueryOperation.EXCLUDES, fields=[5990, 3200]) result = query.find() if result is not None: self.assertEqual(6, len(result['assets'])) - def test_09_assets_base_query_where_includes(self): + def test_13_assets_base_query_where_includes(self): query = self.asset_query.where('title', QueryOperation.INCLUDES, fields=['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']) self.assertEqual({'title': {'$in': ['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']}}, query.parameters) - def test_10_assets_base_query_where_is_less_than(self): + def test_14_assets_base_query_where_is_less_than(self): query = self.asset_query.where('title', QueryOperation.IS_LESS_THAN, fields=['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']) self.assertEqual({'title': {'$lt': ['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']}}, query.parameters) - def test_11_assets_base_query_where_is_less_than_or_equal(self): + def test_15_assets_base_query_where_is_less_than_or_equal(self): query = self.asset_query.where('title', QueryOperation.IS_LESS_THAN_OR_EQUAL, fields=['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']) self.assertEqual({'title': {'$lte': ['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']}}, query.parameters) - def test_12_assets_base_query_where_is_greater_than(self): + def test_16_assets_base_query_where_is_greater_than(self): query = self.asset_query.where('title', QueryOperation.IS_GREATER_THAN, fields=['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']) self.assertEqual({'title': {'$gt': ['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']}}, query.parameters) - def test_13_assets_base_query_where_is_greater_than_or_equal(self): + def test_17_assets_base_query_where_is_greater_than_or_equal(self): query = self.asset_query.where('title', QueryOperation.IS_GREATER_THAN_OR_EQUAL, fields=['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']) self.assertEqual({'title': {'$gte': ['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']}}, query.parameters) - def test_14_assets_base_query_where_matches(self): + def test_18_assets_base_query_where_matches(self): query = self.asset_query.where('title', QueryOperation.MATCHES, fields=['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']) self.assertEqual({'title': {'$regex': ['images_(1).jpg', 'images_(2).jpg', 'images_(3).jpg']}}, query.parameters) - def test_15_environment(self): + def test_19_environment(self): query = self.asset_query.environment("dev") self.assertEqual('dev', query.http_instance.headers['environment']) - def test_16_support_include_fallback(self): + def test_20_asset_query_with_version(self): + query = self.asset_query.environment("dev").version("1") + self.assertEqual({'version': '1'}, query.asset_query_params) + + def test_21_asset_query_with_include_dimension(self): + query = self.asset_query.environment("dev").include_dimension(); + self.assertEqual({'include_dimension': 'true'}, query.asset_query_params) + + def test_22_asset_query_with_relative_url(self): + query = self.asset_query.environment("dev").relative_url(); + self.assertEqual({'relative_urls': 'true'}, query.asset_query_params) + + def test_23_support_include_fallback(self): query = self.asset_query.include_fallback() - result = query.find() - self.assertEqual(True, result) + self.assertEqual({'include_fallback': 'true'}, query.asset_query_params) suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) diff --git a/tests/test_entry.py b/tests/test_entry.py index b7eb842..fb3ec33 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -90,12 +90,26 @@ def test_12_entry_include_reference_github_issue(self): ["categories", "brand"]) response = github_entry.fetch() - - def test_13_entry_support_include_fallback(self): + + def test_13_entry_support_include_fallback_unit_test(self): global entry_uid entry = self.stack.content_type('faq').entry(entry_uid).include_fallback() self.assertEqual(True, entry.entry_param.__contains__('include_fallback')) + def test_14_entry_support_include_fallback_api_test(self): + + api_key = 'blt2585ce4a79ba8bdf' + delivery_token = 'csaa34e51f8cb3e91fb506a469' + environment = 'dev' + dev_host = 'dev9-cdn.contentstack.com' + content_type = 'testincludefallback' + ifb_entry_uid = 'blt50b71bcec5a6b720' + + ifb_stack = contentstack.Stack(api_key, delivery_token, environment, host=dev_host) + entry = ifb_stack.content_type(content_type).entry(ifb_entry_uid) + result = entry.include_fallback().locale('mr-in').fetch() + self.assertEqual('en-us', result['entry']['locale']) + suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) diff --git a/tests/test_query.py b/tests/test_query.py index c8691df..04a772c 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -125,6 +125,19 @@ def test_18_support_include_fallback_url(self): logging.info(query.base_url) self.assertEqual({'include_fallback': 'true'}, query.query_params) + def test_19_entry_support_include_fallback_api_test(self): + + api_key = 'blt2585ce4a79ba8bdf' + delivery_token = 'csaa34e51f8cb3e91fb506a469' + environment = 'dev' + dev_host = 'dev9-cdn.contentstack.com' + content_type = 'testincludefallback' + + ifb_stack = contentstack.Stack(api_key, delivery_token, environment, host=dev_host) + query = ifb_stack.content_type(content_type).query() + result = query.include_fallback().locale('mr-in').find() + self.assertEqual('en-us', result['entries'][0]['locale']) + suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) From b4edf799276f586dce3e57fa5502036cd5fd8da3 Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Mon, 12 Oct 2020 13:54:28 +0530 Subject: [PATCH 4/8] code_coverage improved from 85% to 92% --- README.md | 2 +- README.rst | 4 +- changelog.rst | 2 + contentstack/asset.py | 2 +- contentstack/assetquery.py | 21 +++++++- contentstack/entry.py | 2 +- contentstack/entryqueryable.py | 2 +- contentstack/query.py | 4 +- coverage.svg | 6 +-- tests/__init__.py | 1 + tests/credentials.py | 11 +++- tests/test_assets.py | 29 ++++++++-- tests/test_entry.py | 59 +++++++++++++++------ tests/test_query.py | 30 +++++------ tests/test_stack.py | 97 ++++++++++++++++++++++------------ tests/test_utils.py | 35 ++++++++++++ 16 files changed, 222 insertions(+), 85 deletions(-) create mode 100644 tests/test_utils.py diff --git a/README.md b/README.md index 4f2bb9b..72bf245 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) -![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.0.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/8f54ece7ec56cca587488be2080d04a75aa04558/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.1.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) +![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.0.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/70a07f8346fdd5ed71054c017131245b1bca36d2/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) ## Python SDK for Contentstack diff --git a/README.rst b/README.rst index 62edcf4..ae78935 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Setup and Installation .. code-block:: python - Install contentstack pip + install contentstack pip This is the preferred method to install contentstack, as it will always install the most recent stable release. If you don't have `pip `_ @@ -103,7 +103,7 @@ To retrieve multiple entries of a particular content type, use the code snippet *For example:* - if you want to crop an image (with a width of 300 and height of 400), you simply need to append query parameters at the end of the image URL, such as + If you want to crop an image (with a width of 300 and height of 400), you simply need to append query parameters at the end of the image URL, such as .. code-block:: python diff --git a/changelog.rst b/changelog.rst index 66f6c0c..dc370bb 100644 --- a/changelog.rst +++ b/changelog.rst @@ -2,6 +2,8 @@ **CHANGELOG** ================ +ENHANCEMENT, NEW FEATURE, BUG RESOLVE + *v1.2.0* ============ diff --git a/contentstack/asset.py b/contentstack/asset.py index 76ca881..5407f78 100644 --- a/contentstack/asset.py +++ b/contentstack/asset.py @@ -88,7 +88,7 @@ def params(self, key, value): return self def relative_urls(self): - r"""Include the relative URLs of the assets in the response. + """Include the relative URLs of the assets in the response. :return: Asset, so we can chain the call diff --git a/contentstack/assetquery.py b/contentstack/assetquery.py index 9496170..3c2cc00 100644 --- a/contentstack/assetquery.py +++ b/contentstack/assetquery.py @@ -97,9 +97,9 @@ def relative_url(self): """ self.asset_query_params["relative_urls"] = "true" return self - + def include_fallback(self): - r"""Include the fallback locale publish content, if specified locale content is not publish. + """Include the fallback locale publish content, if specified locale content is not publish. :return: AssetQuery, so we can chain the call @@ -114,6 +114,23 @@ def include_fallback(self): self.asset_query_params['include_fallback'] = "true" return self + def locale(self, locale: str): + """Enter locale code. e.g., en-us + This retrieves published entries of specific locale.. + + :return: AssetQuery, so we can chain the call + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> result = stack.asset_query().locale('en-us').find() + ---------------------------- + """ + self.asset_query_params['locale'] = locale + return self + def find(self): r"""This call fetches the list of all the assets of a particular stack. It also returns the content of each asset in JSON format. diff --git a/contentstack/entry.py b/contentstack/entry.py index 9b107bb..089ba8c 100644 --- a/contentstack/entry.py +++ b/contentstack/entry.py @@ -100,7 +100,7 @@ def param(self, key: str, value: any): return self def include_fallback(self): - r"""Include the fallback locale publish content, if specified locale content is not publish. + """Include the fallback locale publish content, if specified locale content is not publish. :return: Entry, so we can chain the call diff --git a/contentstack/entryqueryable.py b/contentstack/entryqueryable.py index 8368c26..8030ed5 100644 --- a/contentstack/entryqueryable.py +++ b/contentstack/entryqueryable.py @@ -73,7 +73,6 @@ def excepts(self, field_uid: str): def include_reference(self, field_uid): """ - **[Include Reference]:** When you fetch an entry of a content type that has a reference field, by default, the content of the referred entry is not fetched. It only fetches the UID of the referred entry, along with the content of @@ -176,3 +175,4 @@ def add_param(self, key: str, value: str): """ if None not in (key, value): self.entry_queryable_param[key] = value + return self diff --git a/contentstack/query.py b/contentstack/query.py index df8c863..e332216 100644 --- a/contentstack/query.py +++ b/contentstack/query.py @@ -186,12 +186,12 @@ def where_not_in(self, key, query_object): return self def include_fallback(self): - r"""Include the fallback locale publish content, if specified locale content is not publish. + """Include the fallback locale publish content, if specified locale content is not publish. :return: Query, so we can chain the call ---------------------------- - Example:: + Example: >>> import contentstack >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') diff --git a/coverage.svg b/coverage.svg index 6963b3e..a8c7e72 100644 --- a/coverage.svg +++ b/coverage.svg @@ -9,13 +9,13 @@ - + coverage coverage - 87% - 87% + 92% + 92% diff --git a/tests/__init__.py b/tests/__init__.py index 769ed0d..8847443 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -2,6 +2,7 @@ # clean all the .pyc files # find . -name \*.pyc -delete # pytest --cov=contentstack +# pytest -v --cov=contentstack --cov-report=html import unittest from unittest import TestLoader, TestSuite from HtmlTestRunner import HTMLTestRunner diff --git a/tests/credentials.py b/tests/credentials.py index 4bd899b..09dcc40 100644 --- a/tests/credentials.py +++ b/tests/credentials.py @@ -6,8 +6,15 @@ # Your code has been rated at 10.00/10 # -------------------------------------------------------------------- +# keys = { +# 'api_key': 'bltc94709340b84bdd2', +# 'delivery_token': 'csd2e69747f83e59e327d19962', +# 'environment': 'development', +# } + keys = { - 'api_key': 'bltc94709340b84bdd2', - 'delivery_token': 'csd2e69747f83e59e327d19962', + 'api_key': 'bltc111db6331e74bac', + 'delivery_token': 'csd7e4b376c3822b06e0e70cdd', 'environment': 'development', + 'host': 'dev9-cdn.contentstack.com', } diff --git a/tests/test_assets.py b/tests/test_assets.py index 84fb09e..fda3c9a 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -16,7 +16,8 @@ def setUp(self): self.api_key = credentials.keys['api_key'] self.delivery_token = credentials.keys['delivery_token'] self.environment = credentials.keys['environment'] - self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.host = credentials.keys['host'] + self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment, host=self.host) self.asset_query = self.stack.asset_query() def test_01_assets_query_initial_run(self): @@ -31,7 +32,8 @@ def test_02_asset_method(self): self.asset = self.stack.asset(uid=asset_uid) result = self.asset.relative_urls().include_dimension().fetch() if result is not None: - self.assertEqual({'height': 171, 'width': 294}, result['asset']['dimension']) + result = result['asset']['dimension'] + self.assertEqual({'height': 315, 'width': 600}, result) def test_03_asset_uid(self): global asset_uid @@ -45,7 +47,7 @@ def test_04_asset_filetype(self): self.asset = self.stack.asset(uid=asset_uid) result = self.asset.fetch() if result is not None: - self.assertEqual('image/jpeg', result['asset']['content_type']) + self.assertEqual('image/png', result['asset']['content_type']) def test_05_remove_environment(self): global asset_uid @@ -65,6 +67,19 @@ def test_07_add_param(self): self.asset.params("paramKey", 'paramValue') print(self.asset.base_url) + def test_071_check_none_coverage(self): + try: + self.asset = self.stack.asset(None) + except Exception as inst: + self.assertEqual('Please provide a valid uid', inst.args[0]) + + def test_072_check_none_coverage_test(self): + try: + self.asset = self.stack.asset(uid=asset_uid) + self.asset.params(2, 'value') + except Exception as inst: + self.assertEqual('Kindly provide valid params', inst.args[0]) + def test_08_support_include_fallback(self): global asset_uid self.asset = self.stack.asset(uid=asset_uid) @@ -149,6 +164,14 @@ def test_23_support_include_fallback(self): query = self.asset_query.include_fallback() self.assertEqual({'include_fallback': 'true'}, query.asset_query_params) + def test_24_default_find_no_fallback(self): + _in = ['ja-jp'] + entry = self.asset_query.locale('ja-jp').find() + self.assertEqual(0, entry['assets'].__len__()) + entry_locale = 'publish_details' in entry + flag = entry_locale in entry['assets'] + self.assertEqual(False, flag) + suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) diff --git a/tests/test_entry.py b/tests/test_entry.py index fb3ec33..87bb23a 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -15,7 +15,9 @@ def setUp(self): self.api_key = credentials.keys['api_key'] self.delivery_token = credentials.keys['delivery_token'] self.environment = credentials.keys['environment'] - self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.host = credentials.keys['host'] + self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment, host=self.host) + # self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) def test_01_run_initial_query(self): query = self.stack.content_type('faq').query() @@ -25,7 +27,7 @@ def test_01_run_initial_query(self): entry_uid = result['entries'][0]['uid'] logging.debug(entry_uid) logging.info(' => query result is: {}'.format(result['entries'])) - self.assertEqual('blt53ca1231625bdde4', result['entries'][0]['uid']) + self.assertEqual(entry_uid, result['entries'][0]['uid']) def test_02_entry_by_uid(self): global entry_uid @@ -33,7 +35,8 @@ def test_02_entry_by_uid(self): result = entry.fetch() if result is not None: logging.info(' => entry result is: {}'.format(result['entry'])) - self.assertEqual('blt53ca1231625bdde4', result['entry']['uid']) + entry_uid = result['entry']['uid'] + self.assertEqual(entry_uid, result['entry']['uid']) def test_03_entry_environment(self): global entry_uid @@ -90,25 +93,49 @@ def test_12_entry_include_reference_github_issue(self): ["categories", "brand"]) response = github_entry.fetch() + print(response) def test_13_entry_support_include_fallback_unit_test(self): global entry_uid entry = self.stack.content_type('faq').entry(entry_uid).include_fallback() self.assertEqual(True, entry.entry_param.__contains__('include_fallback')) - def test_14_entry_support_include_fallback_api_test(self): - - api_key = 'blt2585ce4a79ba8bdf' - delivery_token = 'csaa34e51f8cb3e91fb506a469' - environment = 'dev' - dev_host = 'dev9-cdn.contentstack.com' - content_type = 'testincludefallback' - ifb_entry_uid = 'blt50b71bcec5a6b720' - - ifb_stack = contentstack.Stack(api_key, delivery_token, environment, host=dev_host) - entry = ifb_stack.content_type(content_type).entry(ifb_entry_uid) - result = entry.include_fallback().locale('mr-in').fetch() - self.assertEqual('en-us', result['entry']['locale']) + def test_14_entry_queryable_only(self): + try: + entry = self.stack.content_type('faq').entry(entry_uid).only(4) + result = entry.fetch() + self.assertEqual(None, result['uid']) + except KeyError as e: + if hasattr(e, 'message'): + self.assertEqual("Invalid field_uid provided", e.args[0]) + + def test_15_entry_queryable_excepts(self): + try: + entry = self.stack.content_type('faq').entry(entry_uid).excepts(4) + result = entry.fetch() + self.assertEqual(None, result['uid']) + except KeyError as e: + if hasattr(e, 'message'): + self.assertEqual("Invalid field_uid provided", e.args[0]) + + def test_16_entry_queryable_include_content_type(self): + entry = self.stack.content_type('faq').entry(entry_uid).include_content_type() + self.assertEqual({'include_content_type': 'true', 'include_global_field_schema': 'true'}, + entry.entry_queryable_param) + + def test_17_entry_queryable_include_reference_content_type(self): + entry = self.stack.content_type('faq').entry(entry_uid).include_content_type() + self.assertEqual({'include_content_type': 'true', 'include_global_field_schema': 'true'}, + entry.entry_queryable_param) + + def test_18_entry_queryable_include_reference_content_type_uid(self): + entry = self.stack.content_type('faq').entry(entry_uid).include_reference_content_type_uid() + self.assertEqual({'include_reference_content_type_uid': 'true'}, + entry.entry_queryable_param) + + def test_19_entry_queryable_add_param(self): + entry = self.stack.content_type('faq').entry(entry_uid).add_param('cms', 'contentstack') + self.assertEqual({'cms': 'contentstack'}, entry.entry_queryable_param) suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) diff --git a/tests/test_query.py b/tests/test_query.py index 04a772c..bf367cb 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -17,11 +17,13 @@ def setUp(self): self.api_key = credentials.keys['api_key'] self.delivery_token = credentials.keys['delivery_token'] self.environment = credentials.keys['environment'] - stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) - self.query = stack.content_type('room').query() - self.query1 = stack.content_type('product').query() - self.query2 = stack.content_type('app_theme').query() - self.query3 = stack.content_type('product').query() + self.host = credentials.keys['host'] + self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment, host=self.host) + + self.query = self.stack.content_type('room').query() + self.query1 = self.stack.content_type('product').query() + self.query2 = self.stack.content_type('app_theme').query() + self.query3 = self.stack.content_type('product').query() def test_01_functional_or_in_query_type_common_in_query(self): query1 = self.query1.where("price", QueryOperation.IS_LESS_THAN, fields=90) @@ -125,18 +127,12 @@ def test_18_support_include_fallback_url(self): logging.info(query.base_url) self.assertEqual({'include_fallback': 'true'}, query.query_params) - def test_19_entry_support_include_fallback_api_test(self): - - api_key = 'blt2585ce4a79ba8bdf' - delivery_token = 'csaa34e51f8cb3e91fb506a469' - environment = 'dev' - dev_host = 'dev9-cdn.contentstack.com' - content_type = 'testincludefallback' - - ifb_stack = contentstack.Stack(api_key, delivery_token, environment, host=dev_host) - query = ifb_stack.content_type(content_type).query() - result = query.include_fallback().locale('mr-in').find() - self.assertEqual('en-us', result['entries'][0]['locale']) + def test_19_default_find_one_fallback(self): + _in = ['ja-jp', 'en-us'] + entry = self.query.locale('ja-jp').include_fallback().find_one() + self.assertEqual("Language was not found. Please try again.", entry['error_message']) + entry_locale = 'publish_details' in entry + self.assertEqual(False, entry_locale) suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) diff --git a/tests/test_stack.py b/tests/test_stack.py index 68af03c..998aef3 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -10,7 +10,8 @@ api_key = credentials.keys['api_key'] delivery_token = credentials.keys['delivery_token'] environment = credentials.keys['environment'] -stack_instance = contentstack.Stack(api_key, delivery_token, environment) +host = credentials.keys['host'] +stack_instance = contentstack.Stack(api_key, delivery_token, environment, host=host) class TestStack(unittest.TestCase): @@ -19,23 +20,25 @@ def setUp(self): self.api_key = credentials.keys['api_key'] self.delivery_token = credentials.keys['delivery_token'] self.environment = credentials.keys['environment'] - self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.host = credentials.keys['host'] + self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment, host=self.host) - def test_stack_credentials(self): + def test_01_stack_credentials(self): self.assertEqual(self.environment, stack_instance.environment) self.assertEqual(self.delivery_token, stack_instance.delivery_token) + self.assertEqual(self.environment, stack_instance.environment) self.assertEqual(self.api_key, stack_instance.api_key) - def test_stack_region(self): + def test_02_stack_region(self): stack_region = contentstack.Stack(self.api_key, self.delivery_token, self.environment, region=ContentstackRegion.EU) self.assertEqual('eu-cdn.contentstack.com', stack_region.host) - def test_stack_endpoint(self): + def test_03_stack_endpoint(self): logging.info('endpoint for the stack is {}'.format(self.stack.endpoint)) - self.assertEqual('https://cdn.contentstack.io/v3', self.stack.endpoint) + self.assertEqual("https://{}/v3".format(self.host), self.stack.endpoint) - def test_fail(self): + def test_04_permission_error_api_key(self): try: stack_local = contentstack.Stack('', self.delivery_token, self.environment) self.assertEqual(None, stack_local.api_key) @@ -43,7 +46,7 @@ def test_fail(self): if hasattr(e, 'message'): self.assertEqual("'You are not permitted to the stack without valid Api Key'", e.args[0]) - def test_fail_delivery_token(self): + def test_05_permission_error_delivery_token(self): try: stack = contentstack.Stack(self.api_key, '', self.environment) self.assertEqual(None, stack.delivery_token) @@ -51,23 +54,45 @@ def test_fail_delivery_token(self): if hasattr(e, 'message'): self.assertEqual("'You are not permitted to the stack without valid Delivery Token'", e.args[0]) + def test_05_permission_error_environment(self): + try: + stack = contentstack.Stack(self.api_key, self.delivery_token, '') + self.assertEqual(None, stack.delivery_token) + except PermissionError as e: + if hasattr(e, 'message'): + self.assertEqual("You are not permitted to the stack without valid Environment", e.args[0]) + @unittest.skip("demonstrating skipping") - def test_nothing(self): + def test_06_skip_for_nothing(self): self.fail("shouldn't happen") - @unittest.skipIf('', '') - def test_format(self): - # Tests that work for only a certain version of the library. - pass + def test_07_get_api_key(self): + stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.assertEqual(self.api_key, stack.get_api_key) + + def test_08_get_delivery_token(self): + stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.assertEqual(self.delivery_token, stack.get_delivery_token) + + def test_09_get_environment(self): + stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.assertEqual(self.environment, stack.get_environment) - def test_image_transformation(self): + def test_10_get_headers(self): + stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) + self.assertEqual(True, 'api_key' in stack.headers) + self.assertEqual(True, 'access_token' in stack.get_headers) + self.assertEqual(True, 'environment' in stack.get_headers) + self.assertEqual(3, stack.get_headers.__len__()) + + def test_11_image_transformation(self): image_transform = self.stack.image_transform("cdn.contentstack.io/v3/endpoint", width=230, height=300, other="filter") result_url = image_transform.get_url() logging.info('result url is: {}'.format(result_url)) self.assertEqual('cdn.contentstack.io/v3/endpoint?width=230&height=300&other=filter', result_url) - def test_image_transformation_get_url_with_height_width(self): + def test_12_image_transformation_get_url_with_height_width(self): image_url = 'https://images.contentstack.io/v3/assets/blteae40eb499811073/bltc5064f36b5855343' \ '/59e0c41ac0eddd140d5a8e3e/download' image_transform = self.stack.image_transform(image_url, width=500, height=550) @@ -77,7 +102,7 @@ def test_image_transformation_get_url_with_height_width(self): '/59e0c41ac0eddd140d5a8e3e/download?width=500&height=550', result_url) - def test_image_transformation_get_url_with_format(self): + def test_13_image_transformation_get_url_with_format(self): image_url = 'https://images.contentstack.io/v3/assets/blteae40eb499811073/bltc5064f36b5855343' \ '/59e0c41ac0eddd140d5a8e3e/download' image_transform = self.stack.image_transform(image_url, format='gif') @@ -87,49 +112,53 @@ def test_image_transformation_get_url_with_format(self): '/59e0c41ac0eddd140d5a8e3e/download?format=gif', result_url) - # [ functional test-cases fot Synchronization ] + def test_14_image_transformation_invalid_input(self): + try: + image_transform = self.stack.image_transform('', format='gif') + self.assertEqual(None, image_transform.get_url()) + except PermissionError as e: + if hasattr(e, 'message'): + self.assertEqual("image_url required for the image_transformation", e.args[0]) - # def test_sync_pagination(self): - # result = self.stack.pagination('blt376f0470f9334d8e512f5e') - # if result is not None: - # sync_token = result['sync_token'] - # print(sync_token) - # # self.assertIsNotNone(result['sync_token']) + def test__15_sync_pagination_with_invalid_pagination_token(self): + result = self.stack.pagination('pagination_token') + if result is not None: + self.assertEqual('is not valid.', result['errors']['pagination_token'][0]) - def test_init_sync_no_params(self): + def test_16_initialise_sync(self): result = self.stack.sync_init() if result is not None: logging.info(result['total_count']) self.assertEqual(123, result['total_count']) - def test_init_sync_with_content_type_uid(self): + def test_17_entry_with_sync_token(self): + result = self.stack.sync_token('sync_token') + if result is not None: + self.assertEqual('is not valid.', result['errors']['sync_token'][0]) + + def test_18_init_sync_with_content_type_uid(self): result = self.stack.sync_init(content_type_uid='room') if result is not None: self.assertEqual(29, result['total_count']) - def test_init_sync_with_publish_type(self): + def test_19_init_sync_with_publish_type(self): result = self.stack.sync_init(publish_type='entry_published', content_type_uid='track') if result is not None: self.assertEqual(16, result['total_count']) - def test_init_sync_with_all_params(self): + def test_20_init_sync_with_all_params(self): result = self.stack.sync_init(from_date='2018-01-14T00:00:00.000Z', content_type_uid='track', publish_type='entry_published', locale='en-us', ) if result is not None: self.assertEqual(16, result['total_count']) - def test_sync_token(self): - result = self.stack.sync_token('blt3f16ec623aaa004a2c2539') - sync_token = result['sync_token'] - print(sync_token) - - def test_content_type(self): + def test_21_content_type(self): content_type = self.stack.content_type('application_theme') result = content_type.fetch() if result is not None: self.assertEqual('application_theme', result['content_type']['uid']) - def test_content_types_with_query_param(self): + def test_22_content_types_with_query_param(self): query = {'include_count': 'true'} content_type = self.stack.content_type('application_theme') result = content_type.find(params=query) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..63376b5 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,35 @@ +import unittest + +from HtmlTestRunner import HTMLTestRunner +from contentstack import Utils + + +class TestUtils(unittest.TestCase): + + # def setUp(self): + # self.api_key = credentials.keys['api_key'] + # self.delivery_token = credentials.keys['delivery_token'] + # self.environment = credentials.keys['environment'] + # self.host = credentials.keys['host'] + # self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment, host=self.host) + + def test_01_config_logging(self): + result = Utils.config_logging() + self.assertEqual(None, result) + + def test_02_setup_logger(self): + result = Utils.setup_logger() + self.assertEqual(0, result.level) + + def test_03_log(self): + result = Utils.log('print') + self.assertEqual(None, result) + + def test_04_do_url_encode(self): + result = Utils.do_url_encode({'key': 'value', 'contentstack': 'cms'}) + self.assertEqual('key=value&contentstack=cms', result) + + +suite = unittest.TestLoader().loadTestsFromTestCase(TestUtils) +runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +runner.run(suite) From 7b729f8c77a25e14dc55dd00e9c8478b87fefe93 Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Mon, 12 Oct 2020 13:56:44 +0530 Subject: [PATCH 5/8] 92% coverage --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72bf245..7b23dba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) -![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.0.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/70a07f8346fdd5ed71054c017131245b1bca36d2/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) +![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.0.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) ## Python SDK for Contentstack From fe009c0ef2fe2eb1b7bf1f0ec699d45aeadbfda4 Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Mon, 12 Oct 2020 17:32:27 +0530 Subject: [PATCH 6/8] coverage improvement --- contentstack/https_connection.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/contentstack/https_connection.py b/contentstack/https_connection.py index 91040cf..bb08533 100644 --- a/contentstack/https_connection.py +++ b/contentstack/https_connection.py @@ -54,15 +54,6 @@ def get(self, url): We use requests.get method since we are sending a GET request. The four arguments we pass are url, verify(ssl), timeout, headers """ - - # retry_strategy = Retry( - # total=3, - # status_forcelist=[429, 500, 502, 503, 504], - # method_whitelist=["GET"] - # ) - # adapter = HTTPAdapter(max_retries=retry_strategy) - # http = requests.Session() - # http.mount("https://", adapter) try: self.headers.update(user_agents()) From 927a9399a6325c6c6fd92bbf88a1f0cc512c1afa Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Fri, 11 Dec 2020 12:18:34 +0530 Subject: [PATCH 7/8] include_fallback --- CHANGELOG.md | 8 +-- README.md | 83 ++++++++++++++++++-------------- README.rst | 2 +- changelog.rst | 23 +++++---- contentstack/__init__.py | 1 - contentstack/asset.py | 21 ++++---- contentstack/assetquery.py | 5 +- contentstack/basequery.py | 2 +- contentstack/contenttype.py | 2 +- contentstack/entry.py | 16 +++--- contentstack/entryqueryable.py | 4 ++ contentstack/https_connection.py | 15 +++--- contentstack/query.py | 9 ++-- contentstack/stack.py | 4 +- contentstack/utility.py | 2 +- package.json | 2 +- requirements.txt | 11 +++-- setup.py | 4 +- tests/__init__.py | 3 +- tests/credentials.py | 21 ++++---- tests/test_assets.py | 15 ++++-- tests/test_entry.py | 23 ++++++--- tests/test_query.py | 24 +++++---- tests/test_stack.py | 11 +++-- 24 files changed, 175 insertions(+), 136 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 147b814..5871227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,13 @@ # CHANGELOG -*v1.2.0* +## _v1.2.0_ ============ -_Date: 20-Oct-2020_ +_Date: 08-Dec-2020_ - - include_fallback Support Added - - Timeout support included +- include_fallback Support Added +- Timeout support included - Entry - added support for include_fallback. diff --git a/README.md b/README.md index 7b23dba..2aa2996 100644 --- a/README.md +++ b/README.md @@ -1,101 +1,113 @@ [![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) -![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.0.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) +![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.2.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) -## Python SDK for Contentstack +## `Python SDK for Contentstack` -Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. [Read More](https://www.contentstack.com/). +Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. [`Read More`](https://www.contentstack.com/). Contentstack provides Python SDK to build application on top of Python. Given below is the detailed guide and helpful resources to get started with our Python SDK. -### Prerequisite +### `Prerequisite` -You will need python 3 installed on your machine. You can install it from [here](https://www.python.org/ftp/python/3.7.4/python-3.7.4-macosx10.9.pkg). +You will need python 3 installed on your machine. You can install it from [`here`](https://www.python.org/ftp/python/3.7.4/python-3.7.4-macosx10.9.pkg). -### Setup and Installation +### `Setup and Installation` To use the Contentstack Python SDK to your existing project, perform the steps given below: -### Install contentstack pip +### `Install contentstack pip` + ```pyhton pip install contentstack + ``` + -This is the preferred method to install contentstack, as it will always install the most recent stable release. If you don't have [pip](https://pip.pypa.io/) installed, this [Python installation guide](http://docs.python-guide.org/en/latest/starting/installation/) can guide you through the process +This is the preferred method to install contentstack, as it will always install the most recent stable release. If you don't have [`pip`](https://pip.pypa.io/) installed, this [`Python installation guide`](http://docs.python-guide.org/en/latest/starting/installation/) can guide you through the process -### Key Concepts for using Contentstack +### `Key Concepts for using Contentstack` -#### Stack +#### `Stack` -A stack is like a container that holds the content of your app. Learn more about [Stacks](https://www.contentstack.com/docs/developers/set-up-stack). +A stack is like a container that holds the content of your app. Learn more about [`Stacks`](https://www.contentstack.com/docs/developers/set-up-stack). -#### Content Type +#### `Content Type` -Content type lets you define the structure or blueprint of a page or a section of your digital property. It is a form-like page that gives Content Managers an interface to input and upload content. [Read more](https://www.contentstack.com/docs/developers/create-content-types). +Content type lets you define the structure or blueprint of a page or a section of your digital property. It is a form-like page that gives Content Managers an interface to input and upload content. [`Read more`](https://www.contentstack.com/docs/developers/create-content-types). -#### Entry +#### `Entry` -An entry is the actual piece of content created using one of the defined content types. Learn more about [Entries](https://www.contentstack.com/docs/content-managers/work-with-entries). +An entry is the actual piece of content created using one of the defined content types. Learn more about [`Entries`](https://www.contentstack.com/docs/content-managers/work-with-entries). -#### Asset +#### `Asset` -Assets refer to all the media files (images, videos, PDFs, audio files, and so on) uploaded to Contentstack. These files can be used in multiple entries. Read more about [Assets](https://www.contentstack.com/docs/content-managers/work-with-assets). +Assets refer to all the media files (images, videos, PDFs, audio files, and so on) uploaded to Contentstack. These files can be used in multiple entries. Read more about [`Assets`](https://www.contentstack.com/docs/content-managers/work-with-assets). -#### Environment +#### `Environment` -A publishing environment corresponds to one or more deployment servers or a content delivery destination where the entries need to be published. Learn how to work with [Environments](https://www.contentstack.com/docs/developers/set-up-environments). +A publishing environment corresponds to one or more deployment servers or a content delivery destination where the entries need to be published. Learn how to work with [`Environments`](https://www.contentstack.com/docs/developers/set-up-environments). -### Contentstack Python SDK: 5-minute Quickstart +### `Contentstack Python SDK: 5-minute Quickstart` -#### Initializing your SDK +#### `Initializing your SDK` To initialize the SDK, specify application API key, access token, and environment name of the stack as shown in the snippet given below, You can provide optional parameters for config: - stack = contentstack.Stack('api_key','delivery_token','environment') + ```python + stack = contentstack.Stack('api_key','delivery_token','environment') + ``` To get the API credentials mentioned above, log in to your Contentstack account and then in your top panel navigation, go to Settings > Stack to view the API Key and Access Token. -#### Querying content from your stack +#### `Querying content from your stack` To retrieve a single entry from a content type use the code snippet given below: + ```python stack = contentstack.Stack('api_key','delivery_token','environment') content_type = stack.content_type("content_type_uid") entry = content_type.entry("entry_uid") result = entry.fetch() + ``` -##### Get Multiple Entries +##### `Get Multiple Entries` To retrieve multiple entries of a particular content type, use the code snippet given below: + ```python stack = contentstack.Stack('api_key','delivery_token','environment') query = stack.content_type("content_type_uid").query() result = query.find() + ``` -### Advanced Queries +### `Advanced Queries` You can query for content types, entries, assets and more using our Python API Reference. [Python API Reference Doc](https://www.contentstack.com/docs/platforms/python/api-reference/) -### Working with Images +### `Working with Images` We have introduced Image Delivery APIs that let you retrieve images and then manipulate and optimize them for your digital properties. It lets you perform a host of other actions such as crop, trim, resize, rotate, overlay, and so on. -For example, if you want to crop an image (with width as 300 and height as 400), you simply need to append query parameters at the end of the image URL, such as, https://images.contentstack.io/v3/assets/blteae40eb499811073/bltc5064f36b5855343/59e0c41ac0eddd140d5a8e3e/download?crop=300,400. There are several more parameters that you can use for your images. +For example, if you want to crop an image (with width as 300 and height as 400), you simply need to append query parameters at the end of the image URL, such as, `https://images.contentstack.io/v3/assets/blteae40eb499811073/bltc5064f36b5855343/59e0c41ac0eddd140d5a8e3e/download?crop=300,400`. There are several more parameters that you can use for your images. -[Read Image Delivery API documentation](https://www.contentstack.com/docs/platforms/python/api-reference/). +[`Read Image Delivery API documentation`](https://www.contentstack.com/docs/platforms/python/api-reference/). You can use the Image Delivery API functions in this SDK as well. Here are a few examples of its usage in the SDK. + ```python image = stack.image_transform(url, {'quality': 100}).get_url() image = stack.image_transform(url, {'width': 100, 'height': 100}).get_url() image = stack.image_transform(url, {'auto': 'webp'}).get_url() + ``` -### Using the Sync API with Python SDK +### `Using the Sync API with Python SDK` The Sync API takes care of syncing your Contentstack data with your application and ensures that the data is always up-to-date by providing delta updates. Contentstack’s Python SDK supports Sync API, which you can use to build powerful applications. + ```python stack = contentstack.Stack('api_key','delivery_token','environment') #initialize sync response = stack.sync_init() @@ -105,20 +117,21 @@ The Sync API takes care of syncing your Contentstack data with your application response = stack.pagination('pagination_token') #sync using multiple parameters response = stack.sync_init(publish_type='entry_published', content_type_uid='content_type_uid') + ``` Read through to understand how to use the Sync API with Contentstack Python SDK. -[Using the Sync API with Python SDK](https://www.contentstack.com/docs/developers/python/using-the-sync-api-with-python-sdk) +[`Using the Sync API with Python SDK`](https://www.contentstack.com/docs/developers/python/using-the-sync-api-with-python-sdk) ### Helpful Links -- [Contentstack Website](https://www.contentstack.com) -- [Official Documentation](https://contentstack.com/docs) -- [Content Delivery API Docs](https://www.contentstack.com/docs/developers/apis/content-delivery-api/) +- [`Contentstack Website`](https://www.contentstack.com) +- [`Official Documentation`](https://contentstack.com/docs) +- [`Content Delivery API Docs`](https://www.contentstack.com/docs/developers/apis/content-delivery-api/) ### The MIT License (MIT) -Copyright © 2012-2020 [Contentstack](https://www.contentstack.com/). All Rights Reserved +Copyright © 2012-2020 [`Contentstack`](https://www.contentstack.com/). All Rights Reserved Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -126,4 +139,4 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -- [Content Delivery API Docs](https://contentstack.com/docs/apis/content-delivery-api/) +- [`Content Delivery API Docs`](https://contentstack.com/docs/apis/content-delivery-api/) diff --git a/README.rst b/README.rst index ae78935..7fbf863 100644 --- a/README.rst +++ b/README.rst @@ -62,7 +62,7 @@ Contentstack Python SDK: 5-minute Quickstart .. code-block:: python - stack = contentstack.Stack('api_key', 'access_token', 'environment') + stack = contentstack.Stack('api_key', 'delivery_token', 'environment') To get the API credentials mentioned above, log in to your Contentstack account and then in your top panel navigation, go to Settings > Stack to view the API Key and Access Token. diff --git a/changelog.rst b/changelog.rst index dc370bb..b88faf0 100644 --- a/changelog.rst +++ b/changelog.rst @@ -9,18 +9,17 @@ ENHANCEMENT, NEW FEATURE, BUG RESOLVE **Date: 20-Oct-2020** - - include_fallback Support Added - - Timeout support included - - -**Entry** - - Added support for include_fallback. -**Asset** - - Added support for include_fallback. -**AssetQuery** - - Added support for include_fallback. -**Query** - - Added support for include_fallback. +- **include_fallback support added** +`entry` + - added support for include_fallback. +`asset` + - added support for include_fallback. +`assetquery` + - added support for include_fallback. +`query` + - added support for include_fallback. + +- **Timeout support included** ============ diff --git a/contentstack/__init__.py b/contentstack/__init__.py index 2aa018b..fccaaa8 100644 --- a/contentstack/__init__.py +++ b/contentstack/__init__.py @@ -14,7 +14,6 @@ from .https_connection import HTTPSConnection from .stack import Stack from .utility import Utils -# from contentstack import * __title__ = 'contentstack-python' __author__ = 'Contentstack' diff --git a/contentstack/asset.py b/contentstack/asset.py index 5407f78..ac5cc55 100644 --- a/contentstack/asset.py +++ b/contentstack/asset.py @@ -11,7 +11,7 @@ class Asset: - r"""Asset refer to all the media files (images, videos, PDFs, audio files, and so on).""" + r"""`Asset` refer to all the media files (images, videos, PDFs, audio files, and so on).""" def __init__(self, http_instance, uid=None): self.http_instance = http_instance @@ -22,7 +22,6 @@ def __init__(self, http_instance, uid=None): self.base_url = '{}/assets/{}'.format(self.http_instance.endpoint, self.__uid) if 'environment' in self.http_instance.headers: self.asset_params['environment'] = self.http_instance.headers['environment'] - # self.http_instance.headers.pop('environment') def environment(self, environment): r"""Provide the name of the environment if you wish to retrieve the assets published @@ -30,7 +29,7 @@ def environment(self, environment): :param environment {str} - name of the environment - :return: Asset, so we can chain the call + :return: `Asset`, so we can chain the call ------------------------------- [Example]: @@ -48,7 +47,7 @@ def environment(self, environment): def remove_environment(self): r"""Removes environment from the request params - :return: Asset, so we can chain the call + :return: `Asset`, so we can chain the call ------------------------------- [Example]: @@ -71,7 +70,7 @@ def params(self, key, value): :param value: value of the query parameter - :return: Asset, so we can chain the call + :return: `Asset`, so we can chain the call ----------------------------- Example:: @@ -90,7 +89,7 @@ def params(self, key, value): def relative_urls(self): """Include the relative URLs of the assets in the response. - :return: Asset, so we can chain the call + :return: `Asset`, so we can chain the call ---------------------------- Example:: @@ -108,7 +107,7 @@ def include_dimension(self): r"""Include the dimensions (height and width) of the image in the response. Supported image types: JPG, GIF, PNG, WebP, BMP, TIFF, SVG, and PSD. - :return: Asset, so we can chain the call + :return: `Asset`, so we can chain the call ---------------------------- Example:: @@ -123,9 +122,9 @@ def include_dimension(self): return self def include_fallback(self): - r"""Include the fallback locale publish content, if specified locale content is not publish. - - :return: Asset, so we can chain the call + r"""Retrieve the published content of the fallback locale if an + entry is not localized in specified locale + :return: `Asset`, so we can chain the call ---------------------------- Example:: @@ -133,7 +132,7 @@ def include_fallback(self): >>> import contentstack >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') >>> asset = stack.asset(uid='asset_uid') - >>> asset = asset.include_fallback() + >>> result = asset.include_fallback().fetch() ---------------------------- """ self.asset_params['include_fallback'] = "true" diff --git a/contentstack/assetquery.py b/contentstack/assetquery.py index 3c2cc00..949d94b 100644 --- a/contentstack/assetquery.py +++ b/contentstack/assetquery.py @@ -99,7 +99,8 @@ def relative_url(self): return self def include_fallback(self): - """Include the fallback locale publish content, if specified locale content is not publish. + """Retrieve the published content of the fallback locale if an + entry is not localized in specified locale. :return: AssetQuery, so we can chain the call @@ -111,7 +112,7 @@ def include_fallback(self): >>> result = stack.asset_query().include_fallback().find() ---------------------------- """ - self.asset_query_params['include_fallback'] = "true" + self.asset_query_params['include_fallback'] = 'true' return self def locale(self, locale: str): diff --git a/contentstack/basequery.py b/contentstack/basequery.py index 5906397..88ad35a 100644 --- a/contentstack/basequery.py +++ b/contentstack/basequery.py @@ -165,7 +165,7 @@ def query(self, key: str, value): key {str} -- key of the query param value {any} -- value of query param Raises: - KeyError: when key or value found None + `KeyError`: when key or value found None Returns: self-- Class instance, So that method chaining can be performed """ diff --git a/contentstack/contenttype.py b/contentstack/contenttype.py index 0005ac4..0e1a8b2 100644 --- a/contentstack/contenttype.py +++ b/contentstack/contenttype.py @@ -5,7 +5,7 @@ content type. """ -# ************* Module asset ************** +# ************* Module ContentType ************** # Your code has been rated at 10.00/10 by pylint from urllib import parse diff --git a/contentstack/entry.py b/contentstack/entry.py index 089ba8c..f506e74 100644 --- a/contentstack/entry.py +++ b/contentstack/entry.py @@ -8,8 +8,8 @@ from contentstack.entryqueryable import EntryQueryable -# ************* Module stack ************** -# Your code has been rated at 9.89/10 by pylint +# ************* Module Entry ************** +# Your code has been rated at 10/10 by pylint class Entry(EntryQueryable): @@ -20,7 +20,7 @@ class Entry(EntryQueryable): Entry works with version={version_number} environment={environment_name} - locale={locale_code + locale={locale_code} """ def __init__(self, http_instance, content_type_uid, entry_uid): @@ -98,12 +98,11 @@ def param(self, key: str, value: any): raise ValueError('Kindly provide valid key and value arguments') self.entry_param[key] = value return self - - def include_fallback(self): - """Include the fallback locale publish content, if specified locale content is not publish. + def include_fallback(self): + """Retrieve the published content of the fallback locale if an entry is + not localized in specified locale. :return: Entry, so we can chain the call - ---------------------------- Example:: @@ -115,7 +114,8 @@ def include_fallback(self): >>> result = entry.fetch() ---------------------------- """ - self.entry_param['include_fallback'] = "true" + print('Requesting fallback....') + self.entry_param['include_fallback'] = 'true' return self def __get_base_url(self): diff --git a/contentstack/entryqueryable.py b/contentstack/entryqueryable.py index 8030ed5..fb747d6 100644 --- a/contentstack/entryqueryable.py +++ b/contentstack/entryqueryable.py @@ -4,6 +4,10 @@ """ +# ************* Module EntryQueryable ************** +# Your code has been rated at 10/10 by pylint + + class EntryQueryable: """ This class is base class for the Entry and Query class that shares common functions diff --git a/contentstack/https_connection.py b/contentstack/https_connection.py index bb08533..ddf61bb 100644 --- a/contentstack/https_connection.py +++ b/contentstack/https_connection.py @@ -7,13 +7,12 @@ import platform from json import JSONDecodeError - +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry import requests from requests.exceptions import Timeout, HTTPError -import contentstack -# from requests.adapters import HTTPAdapter -# from requests.packages.urllib3.util.retry import Retry +import contentstack def get_os_platform(): @@ -54,10 +53,14 @@ def get(self, url): We use requests.get method since we are sending a GET request. The four arguments we pass are url, verify(ssl), timeout, headers """ - try: self.headers.update(user_agents()) - response = requests.get(url, verify=True, timeout=(10, 8), headers=self.headers) + # session = requests.Session() + # retry = Retry(connect=3, backoff_factor=0.5) + # adapter = HTTPAdapter(max_retries=retry) + # session.mount('https://', adapter) + # response = session.get(url, verify=True, headers=self.headers) + response = requests.get(url, verify=True, headers=self.headers) response.encoding = 'utf-8' return response.json() except Timeout: diff --git a/contentstack/query.py b/contentstack/query.py index e332216..bc4f13b 100644 --- a/contentstack/query.py +++ b/contentstack/query.py @@ -8,7 +8,8 @@ from contentstack.entryqueryable import EntryQueryable -# Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) +# ************* Module query.py ************** +# Your code has been rated at 10.00/10 by pylint class QueryType(enum.Enum): @@ -184,9 +185,11 @@ def where_not_in(self, key, query_object): else: raise ValueError('Invalid Key or Value provided') return self - + + def include_fallback(self): - """Include the fallback locale publish content, if specified locale content is not publish. + """Retrieve the published content of the fallback locale if an + entry is not localized in specified locale. :return: Query, so we can chain the call diff --git a/contentstack/stack.py b/contentstack/stack.py index 5cc6f77..407401f 100644 --- a/contentstack/stack.py +++ b/contentstack/stack.py @@ -127,7 +127,7 @@ def asset(self, uid): >>> import contentstack >>> stack = Stack('api_key', 'delivery_token', 'environment') >>> asset_instance = stack.asset(uid='asset_uid') - >>> asset = asset_instance.fetch() + >>> result = asset_instance.fetch() ----------------------------- """ if uid is None or not isinstance(uid, str): @@ -151,7 +151,7 @@ def asset_query(self): def sync_init(self, content_type_uid=None, from_date=None, locale=None, publish_type=None): """ - Constructs and initialises sync if no params provided else below mentioned params + Constructs and initialises sync if no params provided else below mentioned params can be provided to get the response accordingly :param content_type_uid: subsequent syncs will only include the entries of the specified content_type. diff --git a/contentstack/utility.py b/contentstack/utility.py index 9860bb6..4eef0bc 100644 --- a/contentstack/utility.py +++ b/contentstack/utility.py @@ -6,7 +6,7 @@ """ # ************* Module utility checked using pylint ************** -# Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00) +# Your code has been rated at 10.00/10 import json diff --git a/package.json b/package.json index 5c50514..29534a3 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,4 @@ { "name": "contentstack.python", - "version": "1.1.0" + "version": "1.2.0" } diff --git a/requirements.txt b/requirements.txt index 1385fd6..ffc758e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,8 @@ docutils==0.16 -keyring==21.4.0 -pkginfo==1.5.0.1 -requests==2.24.0 -pip~=20.2.3 +keyring==21.5.0 +pkginfo==1.6.1 +requests==2.25.0 +pip~=20.3.1 py~=1.9.0 -setuptools~=50.3.0 \ No newline at end of file +setuptools~=51.0.0 +html-testRunner==1.2.1 \ No newline at end of file diff --git a/setup.py b/setup.py index 07d3e93..f12c3a4 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ name="Contentstack", status="Active", type="process", - created="17 Jun", + created="09 Jun 2020", keywords=contentstack.__title__, version=contentstack.__version__, author="Contentstack", @@ -31,7 +31,7 @@ url="https://github.com/contentstack/contentstack-python", packages=['contentstack'], license='MIT', - test_suite='tests.all_tests', + test_suite='tests', install_requires=['requests>=1.1.0'], classifiers=[ "License :: OSI Approved :: MIT License", diff --git a/tests/__init__.py b/tests/__init__.py index 8847443..04b91ce 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -25,5 +25,4 @@ def all_tests(): ]) runner = HTMLTestRunner(output='reports') test_suite = unittest.TestLoader().discover('./', '*_test.py', '.') - runner.run(test_suite) - + runner.run(test_suite) \ No newline at end of file diff --git a/tests/credentials.py b/tests/credentials.py index 09dcc40..f478d02 100644 --- a/tests/credentials.py +++ b/tests/credentials.py @@ -6,15 +6,16 @@ # Your code has been rated at 10.00/10 # -------------------------------------------------------------------- -# keys = { -# 'api_key': 'bltc94709340b84bdd2', -# 'delivery_token': 'csd2e69747f83e59e327d19962', -# 'environment': 'development', -# } - keys = { - 'api_key': 'bltc111db6331e74bac', - 'delivery_token': 'csd7e4b376c3822b06e0e70cdd', - 'environment': 'development', - 'host': 'dev9-cdn.contentstack.com', + "api_key": "bltc94709340b84bdd2", + "delivery_token": "csd2e69747f83e59e327d19962", + "environment": "development", + "host": "cdn.contentstack.com", } + +# keys = { +# 'api_key': 'bltc111db6331e74bac', +# 'delivery_token': 'csd7e4b376c3822b06e0e70cdd', +# 'environment': 'development', +# 'host': 'cdn.contentstack.com', +# } \ No newline at end of file diff --git a/tests/test_assets.py b/tests/test_assets.py index fda3c9a..8061897 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -7,7 +7,7 @@ from tests import credentials # global asset_uid -global asset_uid +asset_uid = None class TestAsset(unittest.TestCase): @@ -24,7 +24,7 @@ def test_01_assets_query_initial_run(self): result = self.asset_query.find() if result is not None: global asset_uid - asset_uid = result['assets'][7]['uid'] + self.asset_uid = result['assets'][7]['uid'] self.assertEqual(8, len(result['assets'])) def test_02_asset_method(self): @@ -162,6 +162,7 @@ def test_22_asset_query_with_relative_url(self): def test_23_support_include_fallback(self): query = self.asset_query.include_fallback() + result = query.find() self.assertEqual({'include_fallback': 'true'}, query.asset_query_params) def test_24_default_find_no_fallback(self): @@ -173,6 +174,10 @@ def test_24_default_find_no_fallback(self): self.assertEqual(False, flag) -suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) -runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -runner.run(suite) +# suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) +# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +# runner.run(suite) + +if __name__ == '__main__': + unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) + diff --git a/tests/test_entry.py b/tests/test_entry.py index 87bb23a..a797d79 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -1,12 +1,10 @@ import logging import unittest -from HtmlTestRunner import HTMLTestRunner - +import HtmlTestRunner import contentstack from tests import credentials - -global entry_uid +entry_uid = None class TestEntry(unittest.TestCase): @@ -17,7 +15,6 @@ def setUp(self): self.environment = credentials.keys['environment'] self.host = credentials.keys['host'] self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment, host=self.host) - # self.stack = contentstack.Stack(self.api_key, self.delivery_token, self.environment) def test_01_run_initial_query(self): query = self.stack.content_type('faq').query() @@ -94,6 +91,8 @@ def test_12_entry_include_reference_github_issue(self): "brand"]) response = github_entry.fetch() print(response) + categories = response['entry']['categories'] + self.assertEqual(2, len(categories)) def test_13_entry_support_include_fallback_unit_test(self): global entry_uid @@ -137,7 +136,15 @@ def test_19_entry_queryable_add_param(self): entry = self.stack.content_type('faq').entry(entry_uid).add_param('cms', 'contentstack') self.assertEqual({'cms': 'contentstack'}, entry.entry_queryable_param) + def test_20_entry_include_fallback(self): + content_type = self.stack.content_type('faq') + entry = content_type.entry("878783238783").include_fallback() + result = entry.fetch() + self.assertEqual({'include_fallback': 'true'}, entry.entry_param) + -suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) -runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -runner.run(suite) +# suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) +# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +# runner.run(suite) +if __name__ == '__main__': + unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) diff --git a/tests/test_query.py b/tests/test_query.py index bf367cb..8c050ca 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,7 +1,8 @@ import logging import unittest -from HtmlTestRunner import HTMLTestRunner +# from HtmlTestRunner import HTMLTestRunner +import HtmlTestRunner import contentstack from contentstack.basequery import QueryOperation @@ -127,14 +128,17 @@ def test_18_support_include_fallback_url(self): logging.info(query.base_url) self.assertEqual({'include_fallback': 'true'}, query.query_params) - def test_19_default_find_one_fallback(self): - _in = ['ja-jp', 'en-us'] - entry = self.query.locale('ja-jp').include_fallback().find_one() - self.assertEqual("Language was not found. Please try again.", entry['error_message']) - entry_locale = 'publish_details' in entry - self.assertEqual(False, entry_locale) + # def test_19_default_find_fallback(self): + # _in = ['ja-jp', 'en-us'] + # entry = self.query.locale('ja-jp').include_fallback().find() + # self.assertEqual("Language was not found. Please try again.", entry['error_message']) + # entry_locale = 'publish_details' in entry + # self.assertEqual(False, entry_locale) -suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) -runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -runner.run(suite) +# suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) +# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +# runner.run(suite) +if __name__ == '__main__': + unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) + diff --git a/tests/test_stack.py b/tests/test_stack.py index 998aef3..f692a39 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -1,8 +1,7 @@ import logging import unittest -from HtmlTestRunner import HTMLTestRunner - +import HtmlTestRunner import contentstack from contentstack.stack import ContentstackRegion from tests import credentials @@ -167,6 +166,8 @@ def test_22_content_types_with_query_param(self): self.assertEqual(11, result['content_types']['count']) -suite = unittest.TestLoader().loadTestsFromTestCase(TestStack) -runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -runner.run(suite) +# suite = unittest.TestLoader().loadTestsFromTestCase(TestStack) +# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +# runner.run(suite) +if __name__ == '__main__': + unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) From 5793cc9f428b3ca4b34a533a7dbeab413c503dd4 Mon Sep 17 00:00:00 2001 From: shaileshmishra Date: Fri, 11 Dec 2020 14:25:30 +0530 Subject: [PATCH 8/8] Include fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publish content fallback New Features: • [Entry] - Publish fallback method added • [Query] - Publish fallback method added • [Asset] - Publish fallback method added • [Assets] - Publish fallback method added --- README.md | 28 ++++++++++++++-------------- contentstack/https_connection.py | 12 ++++++------ setup.py | 1 + tests/credentials.py | 6 +++--- tests/test_assets.py | 19 ++++++++----------- tests/test_entry.py | 13 +++++-------- tests/test_query.py | 31 ++++++++++++++++--------------- tests/test_stack.py | 6 +++--- 8 files changed, 56 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 2aa2996..cd54990 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,22 @@ [![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) -![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=1.2.0) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) +![Python package](https://github.com/contentstack/contentstack-python/workflows/Python%20package/badge.svg?branch=master) ![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-1.2.0-blue?style=plastic) [![GitHub license](https://img.shields.io/github/license/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/blob/master/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/contentstack/contentstack-python?style=plastic)](https://github.com/contentstack/contentstack-python/stargazers) -## `Python SDK for Contentstack` +## Python SDK for Contentstack Contentstack is a headless CMS with an API-first approach. It is a CMS that developers can use to build powerful cross-platform applications in their favorite languages. Build your application frontend, and Contentstack will take care of the rest. [`Read More`](https://www.contentstack.com/). Contentstack provides Python SDK to build application on top of Python. Given below is the detailed guide and helpful resources to get started with our Python SDK. -### `Prerequisite` +### Prerequisite -You will need python 3 installed on your machine. You can install it from [`here`](https://www.python.org/ftp/python/3.7.4/python-3.7.4-macosx10.9.pkg). +You will need python 3 installed on your machine. You can install it from [here](https://www.python.org/ftp/python/3.7.4/python-3.7.4-macosx10.9.pkg). -### `Setup and Installation` +### Setup and Installation To use the Contentstack Python SDK to your existing project, perform the steps given below: -### `Install contentstack pip` +### Install contentstack pip ```pyhton pip install contentstack @@ -25,31 +25,31 @@ To use the Contentstack Python SDK to your existing project, perform the steps g This is the preferred method to install contentstack, as it will always install the most recent stable release. If you don't have [`pip`](https://pip.pypa.io/) installed, this [`Python installation guide`](http://docs.python-guide.org/en/latest/starting/installation/) can guide you through the process -### `Key Concepts for using Contentstack` +### Key Concepts for using Contentstack -#### `Stack` +#### Stack A stack is like a container that holds the content of your app. Learn more about [`Stacks`](https://www.contentstack.com/docs/developers/set-up-stack). -#### `Content Type` +#### Content Type Content type lets you define the structure or blueprint of a page or a section of your digital property. It is a form-like page that gives Content Managers an interface to input and upload content. [`Read more`](https://www.contentstack.com/docs/developers/create-content-types). -#### `Entry` +#### Entry An entry is the actual piece of content created using one of the defined content types. Learn more about [`Entries`](https://www.contentstack.com/docs/content-managers/work-with-entries). -#### `Asset` +#### Asset Assets refer to all the media files (images, videos, PDFs, audio files, and so on) uploaded to Contentstack. These files can be used in multiple entries. Read more about [`Assets`](https://www.contentstack.com/docs/content-managers/work-with-assets). -#### `Environment` +#### Environment A publishing environment corresponds to one or more deployment servers or a content delivery destination where the entries need to be published. Learn how to work with [`Environments`](https://www.contentstack.com/docs/developers/set-up-environments). -### `Contentstack Python SDK: 5-minute Quickstart` +### Contentstack Python SDK: 5-minute Quickstart -#### `Initializing your SDK` +#### Initializing your SDK To initialize the SDK, specify application API key, access token, and environment name of the stack as shown in the snippet given below, You can provide optional parameters for config: diff --git a/contentstack/https_connection.py b/contentstack/https_connection.py index ddf61bb..31fb16b 100644 --- a/contentstack/https_connection.py +++ b/contentstack/https_connection.py @@ -55,12 +55,12 @@ def get(self, url): """ try: self.headers.update(user_agents()) - # session = requests.Session() - # retry = Retry(connect=3, backoff_factor=0.5) - # adapter = HTTPAdapter(max_retries=retry) - # session.mount('https://', adapter) - # response = session.get(url, verify=True, headers=self.headers) - response = requests.get(url, verify=True, headers=self.headers) + session = requests.Session() + retry = Retry(connect=3, backoff_factor=0.5) + adapter = HTTPAdapter(max_retries=retry) + session.mount('https://', adapter) + response = session.get(url, verify=True, headers=self.headers) + # response = requests.get(url, verify=True, headers=self.headers) response.encoding = 'utf-8' return response.json() except Timeout: diff --git a/setup.py b/setup.py index f12c3a4..400ad74 100644 --- a/setup.py +++ b/setup.py @@ -33,6 +33,7 @@ license='MIT', test_suite='tests', install_requires=['requests>=1.1.0'], + include_package_data=True, classifiers=[ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", diff --git a/tests/credentials.py b/tests/credentials.py index f478d02..8d12c7d 100644 --- a/tests/credentials.py +++ b/tests/credentials.py @@ -10,12 +10,12 @@ "api_key": "bltc94709340b84bdd2", "delivery_token": "csd2e69747f83e59e327d19962", "environment": "development", - "host": "cdn.contentstack.com", + "host": "cdn.contentstack.io", } # keys = { # 'api_key': 'bltc111db6331e74bac', # 'delivery_token': 'csd7e4b376c3822b06e0e70cdd', # 'environment': 'development', -# 'host': 'cdn.contentstack.com', -# } \ No newline at end of file +# 'host': 'cdn.contentstack.io', +# } diff --git a/tests/test_assets.py b/tests/test_assets.py index 8061897..810115e 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -1,13 +1,12 @@ import unittest from HtmlTestRunner import HTMLTestRunner - import contentstack from contentstack.basequery import QueryOperation from tests import credentials # global asset_uid -asset_uid = None +asset_uid = 'bltbac3c14819c8da59' class TestAsset(unittest.TestCase): @@ -25,15 +24,15 @@ def test_01_assets_query_initial_run(self): if result is not None: global asset_uid self.asset_uid = result['assets'][7]['uid'] - self.assertEqual(8, len(result['assets'])) + self.assertEqual(9, len(result['assets'])) def test_02_asset_method(self): - global asset_uid + # global asset_uid self.asset = self.stack.asset(uid=asset_uid) result = self.asset.relative_urls().include_dimension().fetch() if result is not None: result = result['asset']['dimension'] - self.assertEqual({'height': 315, 'width': 600}, result) + self.assertEqual({'height': 50, 'width': 50}, result) def test_03_asset_uid(self): global asset_uid @@ -93,7 +92,7 @@ def test_08_support_include_fallback(self): def test_09_assets_query(self): result = self.asset_query.find() if result is not None: - self.assertEqual(8, len(result['assets'])) + self.assertEqual(9, len(result['assets'])) def test_10_assets_base_query_where_exclude_title(self): query = self.asset_query.where('title', QueryOperation.EXCLUDES, fields=['images_(1).jpg']) @@ -174,10 +173,8 @@ def test_24_default_find_no_fallback(self): self.assertEqual(False, flag) -# suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) -# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -# runner.run(suite) +suite = unittest.TestLoader().loadTestsFromTestCase(TestAsset) +runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +runner.run(suite) -if __name__ == '__main__': - unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) diff --git a/tests/test_entry.py b/tests/test_entry.py index a797d79..951a899 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -1,7 +1,6 @@ import logging import unittest - -import HtmlTestRunner +from HtmlTestRunner import HTMLTestRunner import contentstack from tests import credentials entry_uid = None @@ -140,11 +139,9 @@ def test_20_entry_include_fallback(self): content_type = self.stack.content_type('faq') entry = content_type.entry("878783238783").include_fallback() result = entry.fetch() - self.assertEqual({'include_fallback': 'true'}, entry.entry_param) + self.assertEqual({'environment': 'development', 'include_fallback': 'true'}, entry.entry_param) -# suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) -# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -# runner.run(suite) -if __name__ == '__main__': - unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) +suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) +runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +runner.run(suite) diff --git a/tests/test_query.py b/tests/test_query.py index 8c050ca..f5e56be 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,9 +1,6 @@ import logging import unittest - -# from HtmlTestRunner import HTMLTestRunner -import HtmlTestRunner - +from HtmlTestRunner import HTMLTestRunner import contentstack from contentstack.basequery import QueryOperation from contentstack.query import QueryType @@ -128,17 +125,21 @@ def test_18_support_include_fallback_url(self): logging.info(query.base_url) self.assertEqual({'include_fallback': 'true'}, query.query_params) - # def test_19_default_find_fallback(self): - # _in = ['ja-jp', 'en-us'] - # entry = self.query.locale('ja-jp').include_fallback().find() - # self.assertEqual("Language was not found. Please try again.", entry['error_message']) - # entry_locale = 'publish_details' in entry - # self.assertEqual(False, entry_locale) + def test_19_default_find_without_fallback(self): + _in = ['en-gb', 'en-us'] + entry = self.query.locale('en-gb').find() + uid = entry['entries'][0]['uid'] + self.assertEqual('bltc7911efae23cac48', uid) + self.assertEqual(1, len(entry)) + + def test_20_default_find_with_fallback(self): + _in = ['en-gb', 'en-us'] + entry = self.query.locale('en-gb').include_fallback().find() + entries = entry['entries'] + self.assertEqual(29, len(entries)) -# suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) -# runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) -# runner.run(suite) -if __name__ == '__main__': - unittest.main(testRunner=HtmlTestRunner.HTMLTestRunner(output='example_dir')) +suite = unittest.TestLoader().loadTestsFromTestCase(TestQuery) +runner = HTMLTestRunner(combine_reports=True, add_timestamp=False) +runner.run(suite) diff --git a/tests/test_stack.py b/tests/test_stack.py index f692a39..86fe445 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -128,7 +128,7 @@ def test_16_initialise_sync(self): result = self.stack.sync_init() if result is not None: logging.info(result['total_count']) - self.assertEqual(123, result['total_count']) + self.assertEqual(129, result['total_count']) def test_17_entry_with_sync_token(self): result = self.stack.sync_token('sync_token') @@ -138,12 +138,12 @@ def test_17_entry_with_sync_token(self): def test_18_init_sync_with_content_type_uid(self): result = self.stack.sync_init(content_type_uid='room') if result is not None: - self.assertEqual(29, result['total_count']) + self.assertEqual(30, result['total_count']) def test_19_init_sync_with_publish_type(self): result = self.stack.sync_init(publish_type='entry_published', content_type_uid='track') if result is not None: - self.assertEqual(16, result['total_count']) + self.assertEqual(17, result['total_count']) def test_20_init_sync_with_all_params(self): result = self.stack.sync_init(from_date='2018-01-14T00:00:00.000Z', content_type_uid='track',