diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b3302c..5871227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,44 @@ # CHANGELOG -Date: 10-Aug-2020 - include_reference issue fixed + +## _v1.2.0_ +============ + +_Date: 08-Dec-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/README.md b/README.md index 4f2bb9b..cd54990 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![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=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 -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. @@ -18,31 +18,34 @@ To use the Contentstack Python SDK to your existing project, perform the steps g ### 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 #### 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 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 -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 -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 -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 @@ -50,52 +53,61 @@ A publishing environment corresponds to one or more deployment servers or a cont 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 42ca26c..7fbf863 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', '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** - 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) diff --git a/changelog.rst b/changelog.rst index eebe69c..b88faf0 100644 --- a/changelog.rst +++ b/changelog.rst @@ -1,39 +1,65 @@ -========= -CHANGELOG -========= +================ +**CHANGELOG** +================ + +ENHANCEMENT, NEW FEATURE, BUG RESOLVE + +*v1.2.0* +============ + +**Date: 20-Oct-2020** + +- **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** + +============ + -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..fccaaa8 100644 --- a/contentstack/__init__.py +++ b/contentstack/__init__.py @@ -14,11 +14,10 @@ from .https_connection import HTTPSConnection from .stack import Stack from .utility import Utils -# from contentstack import * __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..ac5cc55 100644 --- a/contentstack/asset.py +++ b/contentstack/asset.py @@ -11,18 +11,17 @@ 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 - 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.http_instance.headers.pop('environment') + self.asset_params['environment'] = self.http_instance.headers['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:: @@ -84,13 +83,13 @@ 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): - 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 + :return: `Asset`, so we can chain the call ---------------------------- Example:: @@ -101,14 +100,14 @@ 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): 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:: @@ -119,7 +118,24 @@ 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"""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:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> asset = stack.asset(uid='asset_uid') + >>> result = asset.include_fallback().fetch() + ---------------------------- + """ + self.asset_params['include_fallback'] = "true" return self def fetch(self): @@ -136,5 +152,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 fb5202d..949d94b 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,41 @@ 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): + """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 + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> result = stack.asset_query().include_fallback().find() + ---------------------------- + """ + 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): @@ -115,6 +149,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/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 fffc2a7..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): @@ -99,6 +99,25 @@ def param(self, key: str, value: any): self.entry_param[key] = value return self + 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:: + + >>> 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() + ---------------------------- + """ + print('Requesting fallback....') + 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): raise KeyError('Provide valid http_instance, content_type_uid or entry_uid') diff --git a/contentstack/entryqueryable.py b/contentstack/entryqueryable.py index b26c4c5..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 @@ -73,7 +77,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 @@ -112,9 +115,7 @@ def include_content_type(self): >>> entry = content_type.entry('uid') >>> entry.include_content_type() >>> result = entry.fetch() - ------------------------------- - [Example: for Query:] >>> import contentstack @@ -178,3 +179,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/https_connection.py b/contentstack/https_connection.py index 5322fdf..31fb16b 100644 --- a/contentstack/https_connection.py +++ b/contentstack/https_connection.py @@ -7,7 +7,8 @@ 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 @@ -54,7 +55,12 @@ def get(self, url): """ try: self.headers.update(user_agents()) - 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/contentstack/query.py b/contentstack/query.py index 242829c..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): @@ -80,71 +81,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 @@ -250,6 +186,27 @@ def where_not_in(self, key, query_object): raise ValueError('Invalid Key or Value provided') return self + + def include_fallback(self): + """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 + + ---------------------------- + 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. List of :class:`Entry ` objects. @@ -267,8 +224,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..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,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..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 @@ -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/coverage.svg b/coverage.svg index 318685c..a8c7e72 100644 --- a/coverage.svg +++ b/coverage.svg @@ -9,13 +9,13 @@ - + coverage coverage - 85% - 85% + 92% + 92% 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 bdf8532..ffc758e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,72 +1,8 @@ -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 -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 +keyring==21.5.0 +pkginfo==1.6.1 +requests==2.25.0 +pip~=20.3.1 +py~=1.9.0 +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..400ad74 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,8 +31,9 @@ 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'], + include_package_data=True, classifiers=[ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", diff --git a/tests/__init__.py b/tests/__init__.py index 76c5479..04b91ce 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,6 +1,8 @@ # python3 -m unittest tests # 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 @@ -23,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 4bd899b..8d12c7d 100644 --- a/tests/credentials.py +++ b/tests/credentials.py @@ -7,7 +7,15 @@ # -------------------------------------------------------------------- keys = { - 'api_key': 'bltc94709340b84bdd2', - 'delivery_token': 'csd2e69747f83e59e327d19962', - 'environment': 'development', + "api_key": "bltc94709340b84bdd2", + "delivery_token": "csd2e69747f83e59e327d19962", + "environment": "development", + "host": "cdn.contentstack.io", } + +# keys = { +# 'api_key': 'bltc111db6331e74bac', +# 'delivery_token': 'csd7e4b376c3822b06e0e70cdd', +# 'environment': 'development', +# 'host': 'cdn.contentstack.io', +# } diff --git a/tests/test_assets.py b/tests/test_assets.py index 1bd9759..810115e 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -1,12 +1,12 @@ import unittest from HtmlTestRunner import HTMLTestRunner - import contentstack from contentstack.basequery import QueryOperation from tests import credentials -global asset_uid +# global asset_uid +asset_uid = 'bltbac3c14819c8da59' class TestAsset(unittest.TestCase): @@ -15,22 +15,24 @@ 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): result = self.asset_query.find() if result is not None: global asset_uid - asset_uid = result['assets'][7]['uid'] - self.assertEqual(8, len(result['assets'])) + self.asset_uid = result['assets'][7]['uid'] + 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: - self.assertEqual({'height': 171, 'width': 294}, result['asset']['dimension']) + result = result['asset']['dimension'] + self.assertEqual({'height': 50, 'width': 50}, result) def test_03_asset_uid(self): global asset_uid @@ -44,89 +46,135 @@ 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_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_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) + 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'])) + self.assertEqual(9, 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_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({'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) runner.run(suite) + + diff --git a/tests/test_entry.py b/tests/test_entry.py index 7d793ff..951a899 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -1,12 +1,9 @@ import logging import unittest - from HtmlTestRunner import HTMLTestRunner - import contentstack from tests import credentials - -global entry_uid +entry_uid = None class TestEntry(unittest.TestCase): @@ -15,7 +12,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) def test_01_run_initial_query(self): query = self.stack.content_type('faq').query() @@ -25,7 +23,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 +31,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,6 +89,57 @@ def test_12_entry_include_reference_github_issue(self): ["categories", "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 + entry = self.stack.content_type('faq').entry(entry_uid).include_fallback() + self.assertEqual(True, entry.entry_param.__contains__('include_fallback')) + + 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) + + 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({'environment': 'development', 'include_fallback': 'true'}, entry.entry_param) suite = unittest.TestLoader().loadTestsFromTestCase(TestEntry) diff --git a/tests/test_query.py b/tests/test_query.py index b074718..f5e56be 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -1,8 +1,6 @@ import logging import unittest - from HtmlTestRunner import HTMLTestRunner - import contentstack from contentstack.basequery import QueryOperation from contentstack.query import QueryType @@ -17,11 +15,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) @@ -114,8 +114,32 @@ 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) + + 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) + diff --git a/tests/test_stack.py b/tests/test_stack.py index 68af03c..86fe445 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 @@ -10,7 +9,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 +19,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 +45,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 +53,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 +101,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 +111,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']) + self.assertEqual(129, 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']) + self.assertEqual(30, 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']) + self.assertEqual(17, 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) @@ -138,6 +166,8 @@ def test_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')) 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)