diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..a754dc4 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,39 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Upload Python Package + +on: + release: + types: [ published ] + +permissions: + contents: read + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.x' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + - name: Build package + run: python -m build + - name: Publish package + uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + with: + user: __token__ + password: ${{ secrets.PYPI_PASSWORD }} \ No newline at end of file diff --git a/.talismanrc b/.talismanrc index fc94aad..e037c6a 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,6 +1,5 @@ threshold: medium fileignoreconfig: - - filename: README.md - checksum: d9d20b2badbf1aa62649bb174db47a9e491d8687e33ac69fee62568efa23fc56 + checksum: b9cad0b376230ea7079fef3459f233b2c61f3d5e48d6d5d03a6bf3e2d39117a8 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ff306e0..d56c8c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # CHANGELOG +## _v1.8.0_ + +### **Date: 26-MAY-2023** + +- AZURE_EU, Region support added +- Include Metadata support added to asset, entry and query +- General code improvement clean up +- Updated code for Live Preview + --- ## _v1.7.0_ @@ -67,13 +76,13 @@ Date: 08-Dec-2020 - Timeout support included - Entry - - added support for include_fallback. + - added support for include_fallback. - Asset - - added support for include_fallback. + - added support for include_fallback. - AssetQueryable - - added support for include_fallback. + - added support for include_fallback. - Query - - added support for include_fallback. + - added support for include_fallback. --- diff --git a/LICENSE b/LICENSE index 4a492b6..e89ccc6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2012 - 2021 Contentstack. All rights reserved. +Copyright (c) 2012 - 2023 Contentstack. 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 diff --git a/README.md b/README.md index 660d39a..29fdd25 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,23 @@ -# - -[![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) +#[![Contentstack](https://www.contentstack.com/docs/static/images/contentstack.png)](https://www.contentstack.com/) ![build](https://img.shields.io/badge/build-passing-green?style=plastic) -![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-v1.7.0-blue?style=plastic) ![python](https://img.shields.io/badge/python-3.5|3.6|3.7|3.8-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) +![Coverage](https://raw.githubusercontent.com/contentstack/contentstack-python/b4edf799276f586dce3e57fa5502036cd5fd8da3/coverage.svg) ![pip](https://img.shields.io/badge/pip-v1.8.0-blue?style=plastic) ![python](https://img.shields.io/badge/python-3.5|3.6|3.7|3.8-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. +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 -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 @@ -29,48 +31,61 @@ pip install contentstack Install latest [contentstack](https://pypi.org/project/Contentstack) package from the [PyPI](https://pypi.org) -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 #### 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: +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: ```python -stack = contentstack.Stack('api_key','delivery_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. +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: ```python -stack = contentstack.Stack('api_key','delivery_token','environment') +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() @@ -81,7 +96,7 @@ result = entry.fetch() To retrieve multiple entries of a particular content type, use the code snippet given below: ```python -stack = contentstack.Stack('api_key','delivery_token','environment') +stack = contentstack.Stack('api_key', 'delivery_token', 'environment') query = stack.content_type("content_type_uid").query() result = query.find() ``` @@ -94,9 +109,12 @@ You can query for content types, entries, assets and more using our Python API R ### 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. +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/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/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/). @@ -110,13 +128,15 @@ image = stack.image_transform(url, {'auto': 'webp'}).get_url() ### 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. +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() #sycn using sync token -response = stack.sync_token('sync_token') #sycn using pagination token -response = stack.pagination('pagination_token') #sync using multiple parameters +stack = contentstack.Stack('api_key', 'delivery_token', 'environment') # initialize sync +response = stack.sync_init() # sycn using sync token +response = stack.sync_token('sync_token') # sycn using pagination token +response = stack.pagination('pagination_token') # sync using multiple parameters response = stack.sync_init(publish_type='entry_published', content_type_uid='content_type_uid') ``` @@ -132,12 +152,19 @@ Read through to understand how to use the Sync API with Contentstack Python SDK. ### The MIT License (MIT) -Copyright © 2012-2021 [Contentstack](https://www.contentstack.com/). All Rights Reserved +Copyright © 2012-2023 [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: +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: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. -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. +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/) diff --git a/README.rst b/README.rst index 5a7a729..689779e 100644 --- a/README.rst +++ b/README.rst @@ -137,7 +137,7 @@ Read through to understand how to use the Sync API with Contentstack Python SDK. The MIT License (MIT) ^^^^^^^^^^^^^^^^^^^^^ - Copyright © 2012-2020 Contentstack. 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: + Copyright © 2012-2023 Contentstack. 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: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..88da02e --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,27 @@ +# Security + +Contentstack takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations. + +If you believe you have found a security vulnerability in any Contentstack-owned repository, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Send email to [security@contentstack.com](mailto:security@contentstack.com). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +[https://www.contentstack.com/trust/](https://www.contentstack.com/trust/) diff --git a/changelog.rst b/changelog.rst index 5cac609..fa53393 100644 --- a/changelog.rst +++ b/changelog.rst @@ -2,11 +2,21 @@ **CHANGELOG** ================ -*v1.7.0* +*v1.8.0* +============ + +**Date: 26-MAY-2023** +- AZURE_EU, Region support added +- Include Metadata support added to asset, entry and query +- General code improvement clean up +- Updated code for Live Preview + + +*v1.7.0* ============ -**Date: 8-APR-2022** +**Date: 08-APR-2022** Region support added. - AZURE_NA support added diff --git a/contentstack/__init__.py b/contentstack/__init__.py index 574510d..dad2904 100644 --- a/contentstack/__init__.py +++ b/contentstack/__init__.py @@ -2,11 +2,6 @@ The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path - -Used: Safety checks your installed dependencies for known security vulnerabilities -file __init__.py contains package information like -__author__, __status__, __version__, __endpoint__ and __email__ - """ from .entry import Entry from .asset import Asset @@ -16,8 +11,9 @@ from .utility import Utils __title__ = 'contentstack-python' -__author__ = 'Contentstack' +__author__ = 'contentstack' __status__ = 'debug' -__version__ = '1.7.0' +__version__ = 'v1.8.0' __endpoint__ = 'cdn.contentstack.io' -__email__ = 'shailesh.mishra@contentstack.com' +__email__ = 'mobile@contentstack.com' +__developer_email__ = 'shailesh.mishra@contentstack.com' diff --git a/contentstack/asset.py b/contentstack/asset.py index 459f418..601f406 100644 --- a/contentstack/asset.py +++ b/contentstack/asset.py @@ -4,9 +4,6 @@ These files can be attached and used in multiple entries. """ -# ************* Module asset ************** -# Your code has been rated at 10/10 by pylint - import logging from urllib import parse @@ -22,8 +19,7 @@ def __init__(self, http_instance, uid=None): 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) + self.base_url = f'{self.http_instance.endpoint}/assets/{self.__uid}' if 'environment' in self.http_instance.headers: self.asset_params['environment'] = self.http_instance.headers['environment'] @@ -133,5 +129,5 @@ def fetch(self): >>> result = asset.fetch() ------------------------------ """ - url = '{}?{}'.format(self.base_url, parse.urlencode(self.asset_params)) + url = f'{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 d986a4e..c3a4b6c 100644 --- a/contentstack/assetquery.py +++ b/contentstack/assetquery.py @@ -3,9 +3,6 @@ You can also specify the environment of which you wish to get the assets. """ -# ************* Module assetquery ************** -# Your code has been rated at 10/10 by pylint - import json import logging @@ -129,6 +126,22 @@ def include_fallback(self): self.asset_query_params['include_fallback'] = 'true' return self + def include_metadata(self): + """Retrieve the metadata in the response. + + :return: AssetQuery, so we can chain the call + + ---------------------------- + Example:: + + >>> import contentstack + >>> stack = contentstack.Stack('api_key', 'delivery_token', 'environment') + >>> result = stack.asset_query().include_metadata().find() + ---------------------------- + """ + self.asset_query_params['include_metadata'] = 'true' + return self + def locale(self, locale: str): """Enter locale code. e.g., en-us This retrieves published entries of specific locale.. diff --git a/contentstack/basequery.py b/contentstack/basequery.py index eff6ed9..169a8bc 100644 --- a/contentstack/basequery.py +++ b/contentstack/basequery.py @@ -4,9 +4,6 @@ log = logging.getLogger(__name__) -# ************* Module basequery.py ************** -# Your code has been rated at 10.00/10 - class QueryOperation(enum.Enum): """ QueryOperation is enum that Provides Options to perform operation to query the result. @@ -160,7 +157,7 @@ def add_params(self, param: dict): def query(self, key: str, value): """ - Adds key value pairs to the to the query parameters + Adds key value pairs to the query parameters Arguments: key {str} -- key of the query param value {any} -- value of query param diff --git a/contentstack/contenttype.py b/contentstack/contenttype.py index 66c5d13..c6eb2f4 100644 --- a/contentstack/contenttype.py +++ b/contentstack/contenttype.py @@ -91,10 +91,9 @@ def fetch(self): raise KeyError( 'content_type_uid can not be None to fetch contenttype') self.local_param['environment'] = self.http_instance.headers['environment'] - uri = '{}/content_types/{}'.format( - self.http_instance.endpoint, self.__content_type_uid) + uri = f'{self.http_instance.endpoint}/content_types/{self.__content_type_uid}' encoded_params = parse.urlencode(self.local_param) - url = '{}?{}'.format(uri, encoded_params) + url = f'{uri}?{encoded_params}' result = self.http_instance.get(url) return result @@ -117,7 +116,7 @@ def find(self, params=None): if params is not None: self.local_param.update(params) encoded_params = parse.urlencode(self.local_param) - url = '{}?{}'.format( - '{}/content_types'.format(self.http_instance.endpoint), encoded_params) + endpoint = self.http_instance.endpoint + url = f'{endpoint}/content_types?{encoded_params}' result = self.http_instance.get(url) return result diff --git a/contentstack/controller.py b/contentstack/controller.py new file mode 100644 index 0000000..e03a900 --- /dev/null +++ b/contentstack/controller.py @@ -0,0 +1,34 @@ +import requests +from requests.utils import guess_json_utf + + +class RequestError(Exception): + def __int__(self, error): + self.error = error['error'] + self.error_code = error['error_code'] + self.error_message = error['error_message'] + + pass + + +def get_request(session, url, headers, timeout): + try: + response = session.get(url, verify=True, headers=headers, timeout=timeout) + if response.encoding is None: + response.encoding = 'utf-8' + except requests.exceptions.RequestException as e: + error = { + 'error': f"Failed to connect to {url}: {str(e)}", + 'error_code': '400', + 'error_message': {str(e)} + } + raise RequestError(error) + except Exception as e: + error = { + 'error': f"An unexpected error while making request to {url}, '400', {str(e)}", + 'error_code': '400', + 'error_message': {str(e)} + } + raise RequestError(error) + else: + return response.json() diff --git a/contentstack/deep_merge_lp.py b/contentstack/deep_merge_lp.py new file mode 100644 index 0000000..1a0e58f --- /dev/null +++ b/contentstack/deep_merge_lp.py @@ -0,0 +1,21 @@ +class DeepMergeMixin: + + def __init__(self, entry_response, lp_response): + self.entry_response = entry_response + self.lp_response = lp_response + + for lp_obj in self.lp_response: + uid = lp_obj.get("uid") + matching_objs = [entry_obj for entry_obj in entry_response if entry_obj.get("uid") == uid] + if matching_objs: + for matching_obj in matching_objs: + self._deep_merge(lp_obj, matching_obj) + + def _deep_merge(self, source, destination): + for key, value in source.items(): + if isinstance(value, dict): + node = destination.setdefault(key, {}) + self._deep_merge(value, node) + else: + destination[key] = value + return destination diff --git a/contentstack/entry.py b/contentstack/entry.py index 142b48e..012fdca 100644 --- a/contentstack/entry.py +++ b/contentstack/entry.py @@ -2,18 +2,15 @@ The Get a single entry request fetches a particular entry of a content type. API Reference: https://www.contentstack.com/docs/developers/apis/content-delivery-api/#single-entry """ - +#min-similarity-lines=10 import logging from urllib import parse -import empty as empty +import empty +from contentstack.deep_merge_lp import DeepMergeMixin from contentstack.entryqueryable import EntryQueryable -# ************* Module Entry ************** -# Your code has been rated at 10/10 by pylint - - log = logging.getLogger(__name__) @@ -39,9 +36,9 @@ def __init__(self, http_instance, content_type_uid, entry_uid): def environment(self, environment): """ - Enter the name of the environment of which the entries needs to be included + Enter the name of the environment of which the entries need to be included Example: production - :param environment: {str} name of the environment of which the entries needs to be included. + :param environment: {str} name of the environment of which the entries need to be included. :return: Entry, so you can chain this call. ------------------------------ Example:: @@ -87,7 +84,7 @@ def param(self, key, value): This method is useful to add additional Query parameters to the entry :param key: {str} -- key The key as string which needs to be added to an Entry :param value: {object} -- value The value as string which needs to be added to an Entry - :return: Entry, so you can chain this call. + :return: @Entry, so you can chain this call. ----------------------------- Example:: @@ -168,15 +165,6 @@ def __get_base_url(self, endpoint=''): url = f'{self.http_instance.endpoint}/content_types/{self.content_type_id}/entries/{self.entry_uid}' return url - def __validate_live_preview(self): - live_preview = self.http_instance.live_preview - if 'enable' in live_preview and live_preview['enable'] \ - and self.content_type_id == live_preview['content_type_uid']: - if 'live_preview' in live_preview: - self.entry_param['live_preview'] = live_preview['live_preview'] - else: - self.entry_param['live_preview'] = 'init' - def fetch(self): """ Fetches the latest version of the entries from stack @@ -191,11 +179,35 @@ def fetch(self): >>> result = entry.fetch() ------------------------------- """ - self.__validate_live_preview() if 'environment' in self.http_instance.headers: self.entry_param['environment'] = self.http_instance.headers['environment'] if len(self.entry_queryable_param) > 0: self.entry_param.update(self.entry_queryable_param) - encoded_string = parse.urlencode(self.entry_param, doseq=True) - url = f'{self.base_url}?{encoded_string}' - return self.http_instance.get(url) + encoded_str = parse.urlencode(self.entry_param, doseq=True) + url = f'{self.base_url}?{encoded_str}' + self._impl_live_preview() + response = self.http_instance.get(url) + if self.http_instance.live_preview is not None and not 'errors' in response: + self.http_instance.live_preview['entry_response'] = response['entry'] + return self._merged_response() + return response + + def _impl_live_preview(self): + lv = self.http_instance.live_preview + if lv is not None and lv['enable'] and 'content_type_uid' in lv and lv[ + 'content_type_uid'] == self.content_type_id: + url = lv['url'] + self.http_instance.headers['authorization'] = lv['management_token'] + lp_resp = self.http_instance.get(url) + if lp_resp is not None and not 'error_code' in lp_resp: + self.http_instance.live_preview['lp_response'] = lp_resp + return None + return None + + def _merged_response(self): + if 'entry_response' in self.http_instance.live_preview and 'lp_response' in self.http_instance.live_preview: + entry_response = self.http_instance.live_preview['entry_response']['entry'] + lp_response = self.http_instance.live_preview['lp_response'] + merged_response = DeepMergeMixin(entry_response, lp_response) + return merged_response.entry_response + pass diff --git a/contentstack/entryqueryable.py b/contentstack/entryqueryable.py index 3b24cb2..cb917c6 100644 --- a/contentstack/entryqueryable.py +++ b/contentstack/entryqueryable.py @@ -4,9 +4,6 @@ """ import logging -# ************* Module EntryQueryable ************** -# Your code has been rated at 10/10 by pylint - log = logging.getLogger(__name__) @@ -157,6 +154,20 @@ def include_reference_content_type_uid(self): self.entry_queryable_param['include_reference_content_type_uid'] = 'true' return self + def include_metadata(self): + """ + This method also includes the metadata in the response + [Example for Query] + >>> 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_metadata() + >>> result = query.find() + """ + self.entry_queryable_param['include_metadata'] = 'true' + return self + def add_param(self, key: str, value: str): """ This method adds key and value to an Entry. diff --git a/contentstack/https_connection.py b/contentstack/https_connection.py index 286e18c..1caf336 100644 --- a/contentstack/https_connection.py +++ b/contentstack/https_connection.py @@ -2,23 +2,17 @@ This module implements the Requests API. """ -# ************* Module https_connection.py ************** -# Your code has been rated at 10.00/10 by pylint - import logging import platform -import json -from json import JSONDecodeError import requests from requests.adapters import HTTPAdapter -from requests.exceptions import HTTPError, Timeout import contentstack +from contentstack.controller import get_request log = logging.getLogger(__name__) def __get_os_platform(): - """ Returns client platform """ os_platform = platform.system() if os_platform == 'Darwin': os_platform = 'macOS' @@ -31,60 +25,26 @@ def __get_os_platform(): def user_agents(): - """User Agents for the Https""" - header = {'sdk': dict( - name=contentstack.__package__, - version=contentstack.__version__ - ), - 'os': __get_os_platform, - 'Content-Type': 'application/json'} - package = f"contentstack-python/{contentstack.__version__}" + header = {'sdk': dict(name=contentstack.__package__, + version=contentstack.__version__ + ), 'os': __get_os_platform(), 'Content-Type': 'application/json'} + package = f"{contentstack.__title__}/{contentstack.__version__}" return {'User-Agent': str(header), "X-User-Agent": package} class HTTPSConnection: # R0903: Too few public methods - """Make Https Request to fetch the result as per requested url""" - def __init__(self, endpoint, headers, timeout, retry_strategy, live_preview): if None not in (endpoint, headers): + self.session = requests.Session() self.payload = None self.endpoint = endpoint self.headers = headers - self.timeout = timeout # default timeout (period=30) seconds + self.timeout = timeout self.retry_strategy = retry_strategy self.live_preview = live_preview def get(self, url): - - """ - Here we create a response object, `response` which will store the request-response. - 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()) - session = requests.Session() - adapter = HTTPAdapter(max_retries=self.retry_strategy) - session.mount('https://', adapter) - response = session.get( - url, verify=True, headers=self.headers, timeout=self.timeout) - session.close() - if response.encoding is None: - response.encoding = 'utf-8' - elif response is not None: - return response.json() - else: - return {"error": "error details not found", "error_code": 422, - "error_message": "unknown error"} - except Timeout as timeout_err: - raise TimeoutError( - json.dumps({"httpStatus": 408, - "message": f'Timeout error ${timeout_err.strerror}'})) from timeout_err - except ConnectionError as connect_err: - raise ConnectionError(json.dumps({"httpStatus": 503, - "message": f'Service error ${connect_err.strerror}'})) from connect_err - except JSONDecodeError as connection_err: - raise TypeError(json.dumps({"httpStatus": 503, - "message": 'Decoding JSON has failed.'})) from connection_err - except HTTPError as http_err: - raise HTTPError('Http error occurred') from http_err + self.headers.update(user_agents()) + adapter = HTTPAdapter(max_retries=self.retry_strategy) + self.session.mount('https://', adapter) + return get_request(self.session, url, headers=self.headers, timeout=self.timeout) diff --git a/contentstack/image_transform.py b/contentstack/image_transform.py index 159ed28..37a1901 100644 --- a/contentstack/image_transform.py +++ b/contentstack/image_transform.py @@ -8,9 +8,6 @@ import logging -# ************* Module image_transform ************** -# Your code has been rated at 10.00/10 by pylint - log = logging.getLogger(__name__) diff --git a/contentstack/query.py b/contentstack/query.py index 9aeb98b..a381580 100644 --- a/contentstack/query.py +++ b/contentstack/query.py @@ -1,19 +1,19 @@ """ Contentstack provides certain queries that you can use to fetch filtered results """ +#min-similarity-lines=10 import enum import json import logging +import warnings from urllib import parse -import empty as empty +import empty from contentstack.basequery import BaseQuery +from contentstack.deep_merge_lp import DeepMergeMixin from contentstack.entryqueryable import EntryQueryable -# ************* Module query.py ************** -# Your code has been rated at 10.00/10 by pylint - log = logging.getLogger(__name__) @@ -123,6 +123,7 @@ def search(self, value: str): """ This method provides only the entries matching the specified value. + @Deprecated deprecated in 1.7.0, Use #regex instaead Arguments: value {str} -- value used to match or compare Raises: @@ -140,6 +141,7 @@ def search(self, value: str): >>> result = query.find() ------------------------------------- """ + warnings.warn('deprecated in 1.7.0, Use regex function instead') if value is not None: self.query_params["typeahead"] = value return self @@ -260,6 +262,25 @@ def include_embedded_items(self): self.query_params['include_embedded_items[]'] = "BASE" return self + def include_metadata(self): + """include_metadata instance of Query + includes metadata in the response (Entries and Assets) along with entry/entries details. + :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_metadata() + >>> result = query.find() + ---------------------------- + """ + self.query_params['include_metadata'] = 'true' + return self + def find(self): """It fetches the query result. List of :class:`Entry ` objects. @@ -295,15 +316,6 @@ def find_one(self): self.query_params["limit"] = 1 return self.__execute_network_call() - def __validate_live_preview(self): - live_preview = self.http_instance.live_preview - if 'enable' in live_preview and live_preview['enable'] \ - and self.content_type_uid == live_preview['content_type_uid']: - if 'live_preview' in live_preview: - self.query_params['live_preview'] = live_preview['live_preview'] - else: - self.query_params['live_preview'] = 'init' # initialise - def __execute_network_call(self): if len(self.entry_queryable_param) > 0: self.query_params.update(self.entry_queryable_param) @@ -311,8 +323,31 @@ def __execute_network_call(self): self.query_params["query"] = json.dumps(self.parameters) if 'environment' in self.http_instance.headers: self.query_params['environment'] = self.http_instance.headers['environment'] - self.__validate_live_preview() encoded_string = parse.urlencode(self.query_params, doseq=True) url = f'{self.base_url}?{encoded_string}' - # url = '{}?{}'.format(self.base_url, encoded_string) - return self.http_instance.get(url) + self._impl_live_preview() + response = self.http_instance.get(url) + if self.http_instance.live_preview is not None and not 'errors' in response: + self.http_instance.live_preview['entry_response'] = response['entries'] + return self._merged_response() + return response + + def _impl_live_preview(self): + lv = self.http_instance.live_preview + if lv is not None and lv['enable'] and 'content_type_uid' in lv and lv[ + 'content_type_uid'] == self.content_type_uid: + url = lv['url'] + self.http_instance.headers['authorization'] = lv['management_token'] + lp_resp = self.http_instance.get(url) + if lp_resp is not None and not 'error_code' in lp_resp: + self.http_instance.live_preview['lp_response'] = lp_resp + return None + return None + + def _merged_response(self): + if 'entry_response' in self.http_instance.live_preview and 'lp_response' in self.http_instance.live_preview: + entry_response = self.http_instance.live_preview['entry_response']['entries'] + lp_response = self.http_instance.live_preview['lp_response'] + merged_response = DeepMergeMixin(entry_response, lp_response) + return merged_response.entry_response + pass diff --git a/contentstack/stack.py b/contentstack/stack.py index 925cd84..2eedab8 100644 --- a/contentstack/stack.py +++ b/contentstack/stack.py @@ -1,23 +1,17 @@ -""" -Class that wraps the credentials of the authenticated user. Think of -this as a container that holds authentication related data. -""" - import enum import logging from urllib import parse from urllib3.util import Retry -import contentstack from contentstack.asset import Asset from contentstack.assetquery import AssetQuery from contentstack.contenttype import ContentType from contentstack.https_connection import HTTPSConnection from contentstack.image_transform import ImageTransform -__author__ = "ishaileshmishra (ishaileshmishra@gmail.com)" +__author__ = "ishaileshmishra (shailesh.mishra@contentstack.com)" __license__ = "MIT" -__version__ = '1.7.0' +__version__ = '1.8.0' log = logging.getLogger(__name__) DEFAULT_HOST = 'cdn.contentstack.io' @@ -30,6 +24,7 @@ class ContentstackRegion(enum.Enum): US = 'us' EU = 'eu' AZURE_NA = 'azure-na' + AZURE_EU = 'azure-eu' class Stack: @@ -51,7 +46,7 @@ def __init__(self, api_key: str, delivery_token: str, environment: str, branch=None, ): """ - Class that wraps the credentials of the authenticated user. Think of + # Class that wraps the credentials of the authenticated user. Think of this as a container that holds authentication related data. param api_key: api_key of the stack @@ -66,13 +61,13 @@ def __init__(self, api_key: str, delivery_token: str, environment: str, takes input as dictionary object. containing one/multiple key value pair like below. ```python - live_preview = { + live_preview = { 'enable': True, + 'host': 'api.contentstack.io', 'authorization': 'your_management_token', - 'host': 'api.contentstack.com', 'include_edit_tags': True, 'edit_tags_type': object | str, - } + } ``` :param retry_strategy: (optional) custom retry_strategy can be set. Method to create retry_strategy: create object of Retry() and provide the @@ -80,12 +75,11 @@ def __init__(self, api_key: str, delivery_token: str, environment: str, **Example:** >>> _strategy = Retry(total=5, backoff_factor=1, status_forcelist=[408, 429]) + >>> import contentstack >>> stack = contentstack.Stack("api_key", "delivery_token", "environment", - live_preview={enable=True, authorization='your auth token'}, retry_strategy= _strategy) + live_preview={enable=True, authorization='your auth token'}, retry_strategy= _strategy) ``` """ - if live_preview is None: - live_preview = {'enable': False} logging.basicConfig(level=logging.DEBUG) self.headers = {} self._query_params = {} @@ -101,25 +95,29 @@ def __init__(self, api_key: str, delivery_token: str, environment: str, self.timeout = timeout self.branch = branch self.retry_strategy = retry_strategy - self.live_preview_dict = live_preview - + self.live_preview = live_preview self._validate_stack() def _validate_stack(self): if self.api_key is None or self.api_key == '': raise PermissionError( - 'You are not permitted to the stack without valid APIKey') + 'You are not permitted to the stack without valid APIKey' + ) if self.delivery_token is None or self.delivery_token == "": raise PermissionError( - 'You are not permitted to the stack without valid Delivery Token') + 'You are not permitted to the stack without valid Delivery Token' + ) if self.environment is None or self.environment == "": raise PermissionError( - 'You are not permitted to the stack without valid Environment') + 'You are not permitted to the stack without valid Environment' + ) if self.region.value == 'eu' and self.host == DEFAULT_HOST: self.host = 'eu-cdn.contentstack.com' elif self.region.value == 'azure-na' and self.host == DEFAULT_HOST: self.host = 'azure-na-cdn.contentstack.com' + elif self.region.value == 'azure-eu' and self.host == DEFAULT_HOST: + self.host = 'azure-eu-cdn.contentstack.com' elif self.region.value != 'us': self.host = f'{self.region.value}-{DEFAULT_HOST}' self.endpoint = f'https://{self.host}/{self.version}' @@ -133,28 +131,14 @@ def _validate_stack(self): if self.branch is not None: self.headers['branch'] = self.branch - if self.live_preview_dict is not None: - self._validate_live_preview() self.http_instance = HTTPSConnection( endpoint=self.endpoint, - headers=self.headers, timeout=self.timeout, + headers=self.headers, + timeout=self.timeout, retry_strategy=self.retry_strategy, - live_preview=self.live_preview_dict + live_preview=self.live_preview ) - def _validate_live_preview(self): - if isinstance(self.live_preview_dict, dict): - if 'enable' in self.live_preview_dict and self.live_preview_dict['enable']: - if 'authorization' not in self.live_preview_dict: - raise PermissionError("management token is required") - if 'host' not in self.live_preview_dict: - raise PermissionError("host is required") - self.headers['authorization'] = self.live_preview_dict['authorization'] - self.host = self.live_preview_dict['host'] - self.endpoint = f'https://{self.host}/{self.version}' - self.headers.pop('access_token') - self.headers.pop('environment') - @property def get_api_key(self): """ @@ -195,13 +179,13 @@ def get_live_preview(self): """ :return: live preview dictionary """ - return self.live_preview_dict + return self.live_preview def content_type(self, content_type_uid=None): """ Content type defines the structure or schema of a page or a section of your web or mobile property. - :param content_type_uid: + param content_type_uid: :return: ContentType """ return ContentType(self.http_instance, content_type_uid) @@ -210,7 +194,7 @@ def asset(self, uid): """ Assets refer to all the media files (images, videos, PDFs, audio files, and so on) uploaded in your Contentstack repository for future use. - :param uid: asset_uid of the Asset + param uid: asset_uid of the Asset :return: Asset ----------------------------- @@ -243,7 +227,7 @@ def asset_query(self): def sync_init(self, content_type_uid=None, start_from=None, locale=None, publish_type=None): """ Set init to ‘true’ if you want to sync all the published entries and assets. - This is usually used when the app does not have any content and you want to + This is usually used when the app does not have any content, and you want to get all the content for the first time.\n :param content_type_uid: (optional) content type UID. e.g., products @@ -286,7 +270,7 @@ def pagination(self, pagination_token: str): paginated. It provides pagination token in the response. However, you do not have to use the pagination token manually to get the next batch. - :param pagination_token: + param pagination_token: :return: list of sync items ------------------------------ Example: @@ -304,7 +288,7 @@ def sync_token(self, sync_token): to get the updated content next time. The sync token fetches only the content that was added after your last sync, and the details of the content that was deleted or updated. - :param sync_token: sync_token + param sync_token: sync_token :return: list of Sync Result ------------------------------ [Example]: @@ -319,7 +303,7 @@ def sync_token(self, sync_token): def __sync_request(self): r"""Sends a GET request. - :param url URL for :class:`Request` object. + param url is URL for :class:`Request` object. in the query string for the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. :return: :class:`Response ` object @@ -327,10 +311,9 @@ def __sync_request(self): """ base_url = f'{self.http_instance.endpoint}/stacks/sync' self.sync_param['environment'] = self.http_instance.headers['environment'] - encoded_query = parse.urlencode(self.sync_param) - url = f'{base_url}?{encoded_query}' - result = self.http_instance.get(url) - return result + query = parse.urlencode(self.sync_param) + url = f'{base_url}?{query}' + return self.http_instance.get(url) def image_transform(self, image_url, **kwargs): """ @@ -340,7 +323,7 @@ def image_transform(self, image_url, **kwargs): mobile properties.\n :param image_url: base url on which queries to apply - :param kwargs: append queries to the asset URL. + :param kwargs: to append queries to the asset URL. :return: instance of ImageTransform """ if image_url is None or image_url == '': @@ -351,6 +334,44 @@ def image_transform(self, image_url, **kwargs): def live_preview_query(self, **kwargs): """ live_preview_query accepts key value pair objects to the query - i.e hash and content_type_uid + hash, content_type_uid and entry_uid + Example: + # def live_preview_query(**kwargs): + # kwargs is a dict of the keyword args passed to the function + for key, value in kwargs.iteritems(): + print "%s = %s" % (key, value) + Uses:=> + live_preview_query = ( + 'enable': True, + 'live_preview': '#*#*#*#*#', + 'host': 'your_host', + 'content_type_uid': 'product', + 'entry_uid': 'your_entry_uid', + 'authorization': 'management_token' + ) """ - self.live_preview_dict.update(kwargs) + + if self.live_preview is not None and self.live_preview['enable'] and 'live_preview_query' in kwargs: + self.live_preview.update(**kwargs['live_preview_query']) + query = kwargs['live_preview_query'] + if query is not None: + self.live_preview['live_preview'] = query['live_preview'] + else: + self.live_preview['live_preview'] = 'init' + if 'content_type_uid' in self.live_preview and self.live_preview['content_type_uid'] is not None: + self.live_preview['content_type_uid'] = query['content_type_uid'] + if 'entry_uid' in self.live_preview and self.live_preview['entry_uid'] is not None: + self.live_preview['entry_uid'] = query['entry_uid'] + self._cal_url() + return self + + def _cal_url(self): + host = self.live_preview['host'] + ct = self.live_preview['content_type_uid'] + url = f'https://{host}/v3/content_types/{ct}/entries' + if 'entry_uid' in self.live_preview: + uid = self.live_preview['entry_uid'] + lv = self.live_preview['live_preview'] + url = f'{url}/{uid}?live_preview={lv}' + self.live_preview['url'] = url + pass diff --git a/contentstack/utility.py b/contentstack/utility.py index 1e8142e..6a71f2a 100644 --- a/contentstack/utility.py +++ b/contentstack/utility.py @@ -1,13 +1,8 @@ """ -Utils -contentstack -Last modified by Shailesh Mishra on 06/08/20. +Last modified by ishaileshmishra on 06/08/20. Copyright 2019 Contentstack. All rights reserved. """ -# ************* Module utility checked using pylint ************** -# Your code has been rated at 10.00/10 - import json import logging from urllib import parse @@ -30,9 +25,6 @@ def config_logging(logging_type: logging.WARNING): class Utils: - """ - Utility for the contentstack - """ @staticmethod def config_logging(): diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 65716f6..0000000 --- a/package-lock.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "contentstack.python", - "version": "1.6.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "contentstack.python", - "version": "1.6.0", - "license": "MIT" - } - } -} \ No newline at end of file diff --git a/package.json b/package.json deleted file mode 100644 index eb98218..0000000 --- a/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "contentstack-python", - "version": "1.7.0", - "developer": "ishaileshmishra", - "license": "MIT", - "author": { "name": "shaileshmishra", "email": "ishaileshmishra@gmail.com" }, - "homepage": "www.contentstack.com", - "readme": "./readme" -} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 4d99ab2..89bf6b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,21 +1,64 @@ -twython~=3.9.1 -setuptools~=62.1.0 -urllib3~=1.26.2 -contentstack-utils +twython==3.9.1 +setuptools==67.8.0 +contentstack-utils==1.2.0 python-dateutil==2.8.2 -requests==2.27.1 -coverage==6.3.2 -tox==3.25.0 -virtualenv==20.14.1 -Sphinx==4.5.0 +requests==2.31.0 +coverage==7.2.6 +tox==4.5.1 +virtualenv==20.23.0 +Sphinx==7.0.1 sphinxcontrib-websupport==1.2.4 -pip~=22.0.4 -build~=0.7.0 -wheel~=0.37.1 -py~=1.11.0 -lxml~=4.8.0 +pip==23.1.2 +build==0.10.0 +wheel==0.40.0 +lxml==4.9.2 utils~=1.0.1 -keyring~=23.5.0 -docutils==0.16 +keyring==23.13.1 +docutils==0.20.1 pyparsing~=3.0.8 -config~=0.5.1 \ No newline at end of file +config~=0.5.1 +colorama~=0.4.6 +attrs==23.1.0 +argparse~=1.4.0 +toml~=0.10.2 +Jinja2~=3.1.2 +env~=0.1.0 +filelock~=3.12.0 +pluggy~=1.0.0 +six~=1.16.0 +packaging~=23.1 +pytest==7.3.1 +dill~=0.3.6 +pytz==2023.3 +Babel==2.12.1 +pep517==0.13.0 +tomli~=2.0.1 +Werkzeug==2.3.4 +Flask~=2.3.2 +click~=8.1.3 +MarkupSafe==2.1.2 +blinker~=1.6.2 +itsdangerous~=2.1.2 +isort==5.12.0 +pkginfo==1.9.6 +pylint==2.17.4 +astroid==2.15.5 +mccabe==0.7.0 +platformdirs==3.5.1 +imagesize==1.4.1 +snowballstemmer~=2.2.0 +Pygments~=2.15.1 +wrapt==1.15.0 +certifi==2023.5.7 +oauthlib==3.2.2 +idna==3.4 +chardet~=5.1.0 +alabaster==0.7.13 +zipp==3.15.0 +distlib~=0.3.6 +Empty~=0.4.4 +cachetools~=5.3.0 +tomlkit~=0.11.8 +urllib3==2.0.2 +exceptiongroup~=1.1.1 +iniconfig~=2.0.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 1ce6e25..96cbee3 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ type="process", created="09 Jun 2020", keywords="contentstack-python", - version="1.7.0", + version="1.8.0", author="Contentstack", author_email="shailesh.mishra@contentstack.com", description="Contentstack is a headless CMS with an API-first approach.", diff --git a/tests/__init__.py b/tests/__init__.py index 9d099a2..b7341fd 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,4 @@ -# pytest --html=tests/report/test-report.html +# pytest --html=tests/report/test-report.html # above command runs tests and test reports generates in tests/report location. # nosetests --with-coverage --cover-html # clean all the .pyc files @@ -7,9 +7,7 @@ # pytest --cov=contentstack # pytest -v --cov=contentstack --cov-report=html # pytest --html=tests/report/test-report.html -import unittest from unittest import TestLoader, TestSuite - from .test_assets import TestAsset from .test_entry import TestEntry from .test_query import TestQuery @@ -25,5 +23,5 @@ def all_tests(): test_module_stack, test_module_asset, test_module_entry, - test_module_query + test_module_query, ]) diff --git a/tests/test_assets.py b/tests/test_assets.py index d913cff..ba56939 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -1,51 +1,53 @@ import unittest +from urllib3 import Retry import config import contentstack from contentstack.basequery import QueryOperation +ASSET_UID = '' IMAGE = 'images_(1).jpg' +API_KEY = config.APIKEY +DELIVERY_TOKEN = config.DELIVERYTOKEN +ENVIRONMENT = config.ENVIRONMENT +HOST = config.HOST class TestAsset(unittest.TestCase): - global asset_uid def setUp(self): - self.asset_uid = None self.stack = contentstack.Stack( - config.api_key, config.delivery_token, config.environment, host=config.host) + API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=HOST) self.asset_query = self.stack.asset_query() def test_011_setting_timeout(self): excepted = 13 # setting a custom timeout - self.stack = contentstack.Stack(config.api_key, config.delivery_token, config.environment, host=config.host, - timeout=excepted) + self.stack = contentstack.Stack( + API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=config.host, timeout=excepted) self.assertEqual(excepted, self.stack.timeout) def test_12_setting_timeout_failure(self): try: - excepted = 0.01 # setting a custom timeout - self.stack = contentstack.Stack(config.api_key, config.delivery_token, config.environment, host=config.host, - timeout=excepted) + excepted = 1.00 # setting a custom timeout + self.stack = contentstack.Stack( + API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=config.host, timeout=excepted) self.stack.asset_query().find() except TimeoutError: self.assertEqual('Timeout expired.', TimeoutError.__doc__) def test_013_setting_retry_strategy_unit(self): - from urllib3 import Retry self.stack = contentstack \ - .Stack(config.api_key, config.delivery_token, - config.environment, host=config.host, + .Stack(API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=HOST, retry_strategy=Retry(total=3, backoff_factor=1, status_forcelist=[408])) self.assertEqual(1, self.stack.retry_strategy.backoff_factor) self.assertEqual(3, self.stack.retry_strategy.total) self.assertEqual([408], self.stack.retry_strategy.status_forcelist) def test_014_setting_retry_strategy_api(self): - from urllib3 import Retry - self.stack = contentstack.Stack(config.api_key, config.delivery_token, - config.environment, host=config.host, - retry_strategy=Retry(total=5, backoff_factor=0, status_forcelist=[408, 429])) + self.stack = contentstack.Stack( + API_KEY, DELIVERY_TOKEN, ENVIRONMENT, + host=HOST, + retry_strategy=Retry(total=5, backoff_factor=0, status_forcelist=[408, 429])) self.assertEqual(0, self.stack.retry_strategy.backoff_factor) self.assertEqual(5, self.stack.retry_strategy.total) self.assertEqual( @@ -57,47 +59,44 @@ def test_01_assets_query_initial_run(self): assets = result['assets'] for item in assets: if item['title'] == 'if_icon-72-lightning_316154_(1).png': - self.asset_uid = item['uid'] - global asset_uid - asset_uid = item['uid'] + global ASSET_UID + ASSET_UID = item['uid'] self.assertEqual(8, len(assets)) def test_02_asset_method(self): - self.asset = self.stack.asset(uid=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': 50, 'width': 50}, result) - def test_03_asset_uid(self): - - self.asset = self.stack.asset(uid=asset_uid) + def test_03_ASSET_UID(self): + self.asset = self.stack.asset(uid=ASSET_UID) result = self.asset.fetch() if result is not None: - self.assertEqual(asset_uid, result['asset']['uid']) + self.assertEqual(ASSET_UID, result['asset']['uid']) def test_04_asset_filetype(self): - self.asset = self.stack.asset(uid=asset_uid) + self.asset = self.stack.asset(uid=ASSET_UID) result = self.asset.fetch() if result is not None: self.assertEqual('image/png', result['asset']['content_type']) def test_05_remove_environment(self): - self.asset = self.stack.asset(uid=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_06_add_environment(self): - self.asset = self.stack.asset(uid=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_07_add_param(self): - self.asset = self.stack.asset(uid=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: @@ -107,13 +106,13 @@ def test_071_check_none_coverage(self): def test_072_check_none_coverage_test(self): try: - self.asset = self.stack.asset(uid=asset_uid) + 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): - self.asset = self.stack.asset(uid=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) @@ -207,5 +206,10 @@ def test_24_default_find_no_fallback(self): entry = self.asset_query.locale('ja-jp').find() self.assertIsNotNone(entry) + def test_25_include_metadata(self): + entry = self.asset_query.include_metadata() + self.assertTrue( + self.asset_query.asset_query_params.__contains__('include_metadata')) + # if __name__ == '__main__': # unittest.main() diff --git a/tests/test_entry.py b/tests/test_entry.py index 5bc51ab..990be80 100644 --- a/tests/test_entry.py +++ b/tests/test_entry.py @@ -3,148 +3,143 @@ import config import contentstack -_uid = 'blt53ca1231625bdde4' -api_key = config.api_key -delivery_token = config.delivery_token -environment = config.environment -env_host = config.host +_UID = 'blt53ca1231625bdde4' +API_KEY = config.APIKEY +DELIVERY_TOKEN = config.DELIVERYTOKEN +ENVIRONMENT = config.ENVIRONMENT +HOST = config.HOST class TestEntry(unittest.TestCase): def setUp(self): - self.stack = contentstack.Stack(api_key, delivery_token, environment, host=env_host) + self.stack = contentstack.Stack(API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=HOST) def test_run_initial_query(self): query = self.stack.content_type('faq').query() result = query.find() - global _uid if result is not None: - _uid = result['entries'][0]['uid'] - print(f'the uid is: {_uid}') + self._UID = result['entries'][0]['uid'] + print(f'the uid is: {_UID}') - def test_entry_by_uid(self): - global _uid - entry = self.stack.content_type('faq').entry(_uid) + def test_entry_by_UID(self): + global _UID + entry = self.stack.content_type('faq').entry(_UID) result = entry.fetch() if result is not None: - _uid = result['entry']['uid'] - self.assertEqual(_uid, result['entry']['uid']) + _UID = result['entry']['uid'] + self.assertEqual(_UID, result['entry']['uid']) def test_03_entry_environment(self): - global _uid + global _UID entry = self.stack.content_type('faq').entry( - _uid).environment('test') + _UID).environment('test') self.assertEqual("test", entry.http_instance.headers['environment']) def test_04_entry_locale(self): - global _uid - entry = self.stack.content_type('faq').entry(_uid).locale('en-ei') + global _UID + entry = self.stack.content_type('faq').entry(_UID).locale('en-ei') entry.fetch() self.assertEqual('en-ei', entry.entry_param['locale']) def test_05_entry_version(self): - global _uid - entry = self.stack.content_type('faq').entry(_uid).version(3) + global _UID + entry = self.stack.content_type('faq').entry(_UID).version(3) entry.fetch() self.assertEqual(3, entry.entry_param['version']) def test_06_entry_params(self): - global _uid + global _UID entry = self.stack.content_type('faq').entry( - _uid).param('param_key', 'param_value') + _UID).param('param_key', 'param_value') entry.fetch() self.assertEqual('param_value', entry.entry_param['param_key']) def test_07_entry_base_only(self): - global _uid + global _UID entry = self.stack.content_type( - 'faq').entry(_uid).only('field_uid') + 'faq').entry(_UID).only('field_UID') entry.fetch() self.assertEqual({'environment': 'development', - 'only[BASE][]': 'field_uid'}, entry.entry_param) + 'only[BASE][]': 'field_UID'}, entry.entry_param) def test_08_entry_base_excepts(self): - global _uid + global _UID entry = self.stack.content_type('faq').entry( - _uid).excepts('field_uid') + _UID).excepts('field_UID') entry.fetch() self.assertEqual({'environment': 'development', - 'except[BASE][]': 'field_uid'}, entry.entry_param) + 'except[BASE][]': 'field_UID'}, entry.entry_param) def test_10_entry_base_include_reference_only(self): - global _uid - entry = self.stack.content_type('faq').entry(_uid).only('field1') + global _UID + entry = self.stack.content_type('faq').entry(_UID).only('field1') entry.fetch() self.assertEqual({'environment': 'development', 'only[BASE][]': 'field1'}, entry.entry_param) def test_11_entry_base_include_reference_excepts(self): - global _uid + global _UID entry = self.stack.content_type( - 'faq').entry(_uid).excepts('field1') + 'faq').entry(_UID).excepts('field1') entry.fetch() self.assertEqual({'environment': 'development', 'except[BASE][]': 'field1'}, entry.entry_param) def test_12_entry_include_reference_github_issue(self): stack_for_products = contentstack.Stack( - "api_key", "delivery_token", "environment") + "API_KEY", "DELIVERY_TOKEN", "ENVIRONMENT") _entry = stack_for_products.content_type('product').entry("ENTRY_UI").include_reference( ["categories", "brand"]) response = _entry.fetch() - # print(response) - # categories = response['entry']['categories'] - # self.assertEqual(2, len(categories)) def test_13_entry_support_include_fallback_unit_test(self): - global _uid + global _UID entry = self.stack.content_type('faq').entry( - _uid).include_fallback() + _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(_uid).only(4) + entry = self.stack.content_type('faq').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]) + self.assertEqual("Invalid field_UID provided", e.args[0]) - def test_15_entry_queryable_excepts(self): + def test_entry_queryable_excepts(self): try: - entry = self.stack.content_type('faq').entry(_uid).excepts(4) + entry = self.stack.content_type('faq').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]) + 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( - _uid).include_content_type() + _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): + def test_reference_content_type_uid(self): entry = self.stack.content_type('faq').entry( - _uid).include_reference_content_type_uid() + _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( - _uid).add_param('cms', 'contentstack') + _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() - print(result) self.assertEqual({'environment': 'development', 'include_fallback': 'true'}, entry.entry_param) @@ -152,9 +147,14 @@ def test_21_entry_include_embedded_items(self): content_type = self.stack.content_type('faq') entry = content_type.entry("878783238783").include_embedded_items() result = entry.fetch() - print(result) self.assertEqual({'environment': 'development', 'include_embedded_items[]': 'BASE'}, entry.entry_param) -# if __name__ == '__main__': -# unittest.main() + def test_22_entry_include_metadata(self): + content_type = self.stack.content_type('faq') + entry = content_type.entry("878783238783").include_metadata() + self.assertEqual({'include_metadata': 'true'}, entry.entry_queryable_param) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_live_preview.py b/tests/test_live_preview.py new file mode 100644 index 0000000..deffb75 --- /dev/null +++ b/tests/test_live_preview.py @@ -0,0 +1,215 @@ +import unittest + +import config +import contentstack +from contentstack.deep_merge_lp import DeepMergeMixin + +management_token = 'cs8743874323343u9' +entry_uid = 'blt8743874323343u9' + +_lp_query = { + 'live_preview': '#0#0#0#0#0#0#0#0#0#', + 'content_type_uid': 'product', + 'entry_uid': entry_uid +} +_lp = { + 'enable': True, + 'host': 'api.contentstack.io', + 'management_token': management_token +} + +API_KEY = config.APIKEY +DELIVERY_TOKEN = config.DELIVERYTOKEN +ENVIRONMENT = config.ENVIRONMENT +HOST = config.HOST +ENTRY_UID = config.APIKEY + + +class TestLivePreviewConfig(unittest.TestCase): + + def setUp(self): + self.stack = contentstack.Stack( + API_KEY, DELIVERY_TOKEN, + ENVIRONMENT, host=HOST) + + def test_live_preview_disabled(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview={ + 'enable': False, + 'host': 'api.contentstack.io', + 'management_token': 'string987654321' + }) + self.stack.content_type('product').entry(entry_uid) + self.assertEqual(3, len(self.stack.get_live_preview)) + self.assertFalse(self.stack.get_live_preview['enable']) + self.assertTrue(self.stack.get_live_preview['management_token']) + + def test_021_live_preview_enabled(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp) + self.stack.live_preview_query(live_preview_query=_lp_query) + self.assertIsNotNone(self.stack.live_preview['management_token']) + self.assertEqual(7, len(self.stack.live_preview)) + self.assertEqual('product', self.stack.live_preview['content_type_uid']) + + def test_03_set_host(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp) + self.assertEqual(7, len(self.stack.live_preview)) + self.assertEqual(True, 'enable' in self.stack.live_preview) + + def test_031_set_host_value(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp) + self.stack.live_preview_query(live_preview_query=_lp_query) + self.assertEqual(7, len(self.stack.live_preview)) + self.assertIsNotNone(self.stack.live_preview['host']) + + def test_06_live_preview_query(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp + ) + self.assertEqual(7, len(self.stack.live_preview)) + + def test_07_branching(self): + stack = contentstack.Stack( + 'api_key', 'delivery_token', 'environment', branch='dev_branch') + stack.content_type('product') + self.assertEqual('dev_branch', stack.get_branch) + + def test_08_live_preview_query_hash_included(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp + ) + self.stack.live_preview_query(live_preview_query=_lp_query) + self.assertEqual(7, len(self.stack.live_preview)) + + def test_09_live_preview_query_hash_excluded(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp + ) + self.stack.live_preview_query(live_preview_query=_lp_query) + self.stack.content_type('product').entry(entry_uid=entry_uid) + self.assertEqual(3, len(self.stack.headers)) + self.assertEqual(True, 'access_token' in self.stack.headers) + self.assertEqual(True, 'api_key' in self.stack.headers) + + def test_10_live_preview_check_hash_value(self): + self.stack = contentstack.Stack( + API_KEY, + DELIVERY_TOKEN, + ENVIRONMENT, + live_preview=_lp + ) + self.stack.live_preview_query(live_preview_query=_lp_query) + entry = self.stack.content_type('product').entry(entry_uid=ENTRY_UID) + resp = entry.fetch() + print(resp) + self.assertEqual(6, len(self.stack.headers)) + self.assertEqual(API_KEY, self.stack.headers['api_key']) + self.assertEqual(DELIVERY_TOKEN, self.stack.headers['access_token']) + self.assertEqual(ENVIRONMENT, self.stack.headers['environment']) + + +lp_response = [ + { + "uid": "76743678743", + "comment": "this belongs to live preview object", + "_version": 2, + "locale": "en-us", + "ACL": {}, + "author": [ + { + "uid": "77f3f0ea3630e06", + "_content_type_uid": "author" + } + ] + }, + { + "uid": "7634767463", + "_version": 2, + "locale": "en-us", + "comment": "this belongs to live preview object", + "ACL": {}, + "author": [ + { + "uid": "bltb77f3f0ea3630e06", + "_content_type_uid": "author" + } + ] + } +] + +entry_response = [ + { + "uid": "76743678743", + "_version": 3, + "locale": "en-uk", + "title": "Updated Title", + "comment": "this belongs to entry object", + }, + { + "uid": "47634767463", + "_version": 3, + "locale": "en-uk", + "title": "Updated Title", + "comment": "this belongs to entry object", + }, + { + "uid": "34767463", + "_version": 3, + "locale": "en-us", + "comment": "this belongs to entry object", + "title": "You have received a new merged entry response" + } +] + + +class TestLivePreviewObject(unittest.TestCase): + + def setUp(self): + self.stack = contentstack.Stack( + API_KEY, DELIVERY_TOKEN, + ENVIRONMENT, host=HOST) + + def test_setup_live_preview(self): + self.stack = contentstack.Stack(API_KEY, DELIVERY_TOKEN, ENVIRONMENT, live_preview={ + 'enable': False, + 'host': 'api.contentstack.io', + 'management_token': 'string987654321' + }) + self.stack.content_type('product').entry(entry_uid) + self.assertEqual(3, len(self.stack.get_live_preview)) + self.assertFalse(self.stack.get_live_preview['enable']) + self.assertTrue(self.stack.get_live_preview['management_token']) + + def test_deep_merge_object(self): + merged_response = DeepMergeMixin(entry_response, lp_response) + self.assertTrue(isinstance(merged_response.entry_response, list)) + self.assertEqual(3, len(merged_response.entry_response)) + print(merged_response.entry_response) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_live_preview_config.py b/tests/test_live_preview_config.py deleted file mode 100644 index 4a65aa7..0000000 --- a/tests/test_live_preview_config.py +++ /dev/null @@ -1,124 +0,0 @@ -import unittest - -import config -import contentstack - -_preview = { - 'enable': True, - 'authorization': 'management_token@fake@testing', - 'host': 'cdn.contentstack.io' -} - - -class TestLivePreviewConfig(unittest.TestCase): - - def setUp(self): - self.stack = contentstack.Stack( - config.api_key, config.delivery_token, - config.environment, host=config.host) - - def test_01_live_preview_enabled_(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview) - self.stack.content_type( - 'live_content_type').entry('live_entry_uid') - self.assertEqual(3, len(self.stack.get_live_preview)) - self.assertTrue(self.stack.get_live_preview['enable']) - self.assertTrue(self.stack.get_live_preview['authorization']) - - def test_021_live_preview_enabled_(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview) - self.assertEqual(_preview['authorization'], - self.stack.live_preview_dict['authorization']) - - def test_03_set_host(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview) - self.assertEqual(3, len(self.stack.live_preview_dict)) - self.assertEqual(True, self.stack.live_preview_dict['enable']) - - def test_031_set_host_value(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview) - self.assertEqual(3, len(self.stack.live_preview_dict)) - self.assertEqual(_preview['host'], - self.stack.live_preview_dict['host']) - - def test_06_live_preview_query(self): - _live_preview = { - 'include_edit_tags': True, - 'edit_tags_type': object, - } - _preview.update(_live_preview) - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview - ) - self.assertEqual(5, len(self.stack.live_preview_dict)) - - def test_07_live_preview_query_hash_included(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview - ) - self.stack.live_preview_query( - hash='live_preview', - content_type_uid='fake@content_type') - self.assertEqual(7, len(self.stack.live_preview_dict)) - - def test_08_live_preview_query_hash_excluded(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview - ) - self.stack.live_preview_query( - content_type_uid='_content_type_live_preview') - self.stack.content_type('_content_type_live_preview') \ - .entry(entry_uid='entry_uid_1234') - - self.assertEqual(2, len(self.stack.headers)) - self.assertEqual(False, 'live_preview' in self.stack.headers) - self.assertEqual(True, 'api_key' in self.stack.headers) - - def test_09_live_preview_check_hash_value(self): - self.stack = contentstack.Stack( - config.api_key, - config.delivery_token, - config.environment, - live_preview=_preview - ) - self.stack.live_preview_query( - content_type_uid='_content_type_live_preview') - self.stack.content_type('fake@content_type') \ - .entry(entry_uid='just@fakeit') - - self.assertEqual(2, len(self.stack.headers)) - self.assertEqual(config.api_key, self.stack.headers['api_key']) - - def test_branching(self): - stack = contentstack.Stack( - 'api_key', 'delivery_token', 'environment', branch='dev_branch') - stack.content_type('product') - self.assertEqual('dev_branch', stack.get_branch) - -# if __name__ == '__main__': -# unittest.main() diff --git a/tests/test_query.py b/tests/test_query.py index 82ddd5d..5c1f056 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -5,13 +5,17 @@ from contentstack.basequery import QueryOperation from contentstack.query import QueryType +API_KEY = config.APIKEY +DELIVERY_TOKEN = config.DELIVERYTOKEN +ENVIRONMENT = config.ENVIRONMENT +HOST = config.HOST class TestQuery(unittest.TestCase): def setUp(self): self.const_value = 'Apple Inc.' self.stack = contentstack.Stack( - config.api_key, config.delivery_token, config.environment, host=config.host) + API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=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() @@ -135,5 +139,7 @@ def test_20_default_find_with_fallback(self): entries = entry['entries'] self.assertEqual(0, len(entries)) -# if __name__ == '__main__': -# unittest.main() + def test_21_include_metadata(self): + entry = self.query.locale('en-gb').include_metadata().find() + entries = entry['entries'] + self.assertEqual(0, len(entries)) diff --git a/tests/test_stack.py b/tests/test_stack.py index d98ab22..2094297 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -3,26 +3,28 @@ import contentstack from contentstack.stack import ContentstackRegion -stack_instance = contentstack.Stack( - config.api_key, config.delivery_token, config.environment, host=config.host) +API_KEY = config.APIKEY +DELIVERY_TOKEN = config.DELIVERYTOKEN +ENVIRONMENT = config.ENVIRONMENT +HOST = config.HOST + +stack_instance = contentstack.Stack(API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=HOST) -# pylint(missing-function-docstring) class TestStack(unittest.TestCase): def setUp(self): - self.stack = contentstack.Stack( - config.api_key, config.delivery_token, config.environment, host=config.host) + self.stack = contentstack.Stack(API_KEY, DELIVERY_TOKEN, ENVIRONMENT, host=HOST) def test_01_stack_credentials(self): - self.assertEqual(config.environment, stack_instance.environment) - self.assertEqual(config.delivery_token, stack_instance.delivery_token) - self.assertEqual(config.api_key, stack_instance.api_key) - self.assertEqual(config.host, stack_instance.host) + self.assertEqual(ENVIRONMENT, stack_instance.environment) + self.assertEqual(DELIVERY_TOKEN, stack_instance.delivery_token) + self.assertEqual(API_KEY, stack_instance.api_key) + self.assertEqual(HOST, stack_instance.host) def test_02_stack_region(self): - stack_region = contentstack.Stack(config.api_key, config.delivery_token, config.environment, - region=ContentstackRegion.EU) + stack_region = contentstack.Stack( + API_KEY, DELIVERY_TOKEN, ENVIRONMENT, region=ContentstackRegion.EU) self.assertEqual('eu-cdn.contentstack.com', stack_region.host) def test_03_stack_endpoint(self): @@ -58,10 +60,6 @@ def test_05_permission_error_environment(self): self.assertEqual( "You are not permitted to the stack without valid Environment", e.args[0]) - @unittest.skip("demonstrating skipping") - def test_06_skip_for_nothing(self): - self.fail("shouldn't happen") - def test_07_get_api_key(self): stack = contentstack.Stack( config.api_key, config.delivery_token, config.environment) @@ -124,6 +122,7 @@ def test__15_sync_pagination_with_invalid_pagination_token(self): self.assertEqual( 'is not valid.', result['errors']['pagination_token'][0]) + @unittest.skip('Work in progress') def test_16_initialise_sync(self): result = self.stack.sync_init() if result is not None: diff --git a/tests/test_utils.py b/tests/test_utils.py deleted file mode 100644 index 682392b..0000000 --- a/tests/test_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -import unittest - -from contentstack import Utils - - -class TestUtils(unittest.TestCase): - - 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)