The Delivery Python SDK can be used in Python projects to retrieve content from the Kentico Kontent headless CMS. This is a community project and not an official Kentico SDK. If you find a bug in the SDK or have a feature request, please submit a GitHub issue.
- Installation
- Creating a client
- Requesting items
- Rich Text Resolution
- Enumerating all items
- Requesting content types
- Requesting Taxonomies
- Listing languages
- Transforming images
To install the SDK from the Python Package Index use:
pip install kontent_delivery
To obtain content from Kentico Kontent, you will create an instance of DeliveryClient and pass it your Project ID:
from kontent_delivery.client import DeliveryClient
client = DeliveryClient("your_project_id")
Using a configuration file to pass API keys and client options when creating instances of DeliveryClient is recommended. This can be done by adding a config.py file to the root of your project and importing it to your modules.
The config.py file should be in the JSON format as follows:
project_id ="your_project_id"
delivery_options = {
"preview": False,
"preview_api_key": "enter_key_here",
"secured": False,
"secured_api_key": "enter_key_here",
"timeout": (4,7)
}
Which can be imported and used to build your DelieryClient:
import config
from kontent_delivery.client import DeliveryClient
client = DeliveryClient(config.project_id, options=config.delivery_options)
Option | Values Type | Description |
---|---|---|
preview | bool | Determines whether client will use Kontent's Preview API |
preview_api_key | string | Project Preview API key |
secured | bool | Determines whether client will use Kontent's Secure access API |
secured_api_key | string | Project Secure access API key |
timeout | set | Determines (in seconds) the read and connect timeout threshold for the Python "Requests" library |
retry_attempts | int | Determines the number of times the SDK will Retry the request |
Note: Preview and Secured API cannot be enabled simultaneously.
By default, the SDK uses a retry policy, asking for requested content again in case of an error. The default policy retries the HTTP request if the following status codes are returned:
- 408 - RequestTimeout
- 429 - TooManyRequests
- 500 - InternalServerError
- 502 - BadGateway
- 503 - ServiceUnavailable
- 504 - GatewayTimeout
The SDK will perform a total of 6 attempts at a maximum of 32 seconds to retrieve content before returning a max retry error. The consecutive attempts are delayed with exponential backoff.
To disable the retry policy or set a custom number of attempts, you can set retry_attempts in the config file:
project_id ="your_project_id"
delivery_options = {
"retry_attempts": 0
}
Getting an item from Kontent can be accomplished by using the DeliveryClient's get_content_item method and passing in the content item's codename:
response = client.get_content_item("coffee_processing_techniques")
print(response.codename)
print(response.elements.title.value)
# prints:
# coffee_processing_techniques
# Coffee processing techniques
Using this method will return a ContentItem. To access elements and their values use dot notation in the format:
response.elements.element_name.value
Getting items used in a linked items element on a content item can be done by using the get_linked_items() method and passing in the linked items element's codename:
response = client.get_content_item("coffee_processing_techniques")
linked_items = response.get_linked_items("related_articles")
for item in linked_items:
print(f"name: {item.name}")
# prints:
# On Roasts
# About Us
# etc.
Using this method will return a list of ContentItems.
Attribute | Description |
---|---|
id | An identifying GUID. |
name | The item's display name as seen in the Kontent UI. |
codename | A unique identifying codename set in the Kontent UI. Note: Special characters are replaced with "_". |
language | The language that the returned item variant exists in. |
content_type | The content model used by the returned item. |
last_modified | The date the item was last editted in the Kontent UI. |
collection | The Collection the item is assigned to. |
workflow_step | The step the item is currently in. Note: Non-preview calls will only returned Published items. |
elements | Contains the content item values and element type information. |
modular_content | Linked items and components associated to the returned item. |
api_response | Response object from the request. |
Getting an item listing from Kontent can be accomplished by using the DeliveryClient's get_content_items method:
response = client.get_content_items()
for item in response.items:
print(item.name)
# prints:
# About us
# AeroPress
# AeroPress Filters
# ...
Using the DeliveryClient's get_content_items method will produce a ContentItemListing object that stores each retrieved item as a ContentItem in an "items" attribute.
Attribute | Description |
---|---|
items | Contains the items returned in the response. |
pagination | Dictionary containing the skip, limit, count, and next page parameters. |
skip | Sets the number of objects to skip when requesting a list of objects. |
limit | Sets the number of objects to retrieve in a single request. |
count | The number of items in the response. |
next_page | Contains the next page of results. |
modular_content | Collection of all linked items and components refrenced in the request. |
api_response | Response object from the request. |
Filtering can be used when getting multiple items. Filters use an "element", "operation", "value" pattern. For example, the following filter gets any items with the title "On Roasts":
from kontent_delivery.builders.filter_builder import Filter
response = client.get_content_items(
Filter("elements.title", "[eq]", "On Roasts")
)
Filters can also be used for Kontent's Projection and parameters following the same pattern. If there is no element to act upon, the Filter's first value is empty:
Filter("", "depth", 6)
Operator | Description | Example |
---|---|---|
[eq] | Property value equals the specified value. | Filter("system.type", "[eq]", "coffee") |
[neq] | Property value does not equal the specified value. | Filter("elements.product_name", "[neq]", "Folgers") |
[empty] | Property value is empty. | Filter("elements.altitude", "[empty]", "") |
[nempty] | Property value is not empty. | Filter("elements.country", "[nempty]", "") |
[lt] | Property value is less than the specified value. | Filter("price", "[lt]", "100") |
[lte] | Property value is less than or equal to the specified value. | Filter("price", "[lte]", "10.5") |
[gt] | Property value is greater than the specified value. | Filter("price", "[gt]", "5") |
[gte] | Property value is greater than or equal to the specified value. | Filter("price", "[gte]", "10.5") |
[range] | Property value falls within the specified range of two values, both inclusive. | Filter("elements.price", "[range]", "10.5,50") |
[in] | Property value is in the specified list of values. | Filter("elements.country", "[in]", "Kenya,Brazil,Argentina" |
[nin] | Property value is not in the specified list of values. | Filter("elements.country", "[nin]", "France") |
[contains] | Property with an array of values contains the specified value. | Filter("elements.product_status", "[contains]", "bestseller") |
[any] | Property with an array of values contains at least one value from the specified list of values. | Filter("elements.processing", "[any]", ["wet__washed_","semi_dry"]) |
[all] | Property with an array of values contains all of the specified values. | Filter("elements.sitemap", "[all]", ["coffee","products"]) |
Operator | Description | Example |
---|---|---|
depth | Content items can reference other content items using linked items or rich text elements. These linked items can reference other items recursively. By default, the API returns only one level of linked items. | Filter("", "depth", 6) |
elements | When getting content items or content types, you can specify which elements to return by using the elements query parameter. | Filter("","elements",["name",product_name"]) |
skip | Sets the number of objects to skip when requesting a list of objects. The skip parameter must be combined with the limit parameter to work. | Filter("", "skip", 2) |
limit | Sets the number of objects to retrieve in a single request. | Filter("", "limit", 10) |
language | Determines which language variant of content items to return. | Filter("", "language", "en-US") |
order | Determines the order of the retrieved objects. Use [asc] for ascending and [desc] for descending orders. | Filter("", "order","codename[desc]") |
Multiple filters and parameters can be passed into a single call:
response = client.get_content_items(
Filter("system.type", "[eq]", "coffee"),
Filter("elements.product_name", "[neq]", "Folgers"),
Filter("price", "[lte]", "10.5"),
Filter("elements.country", "[in]", ["Kenya","Brazil","Argentina"]),
Filter("","depth", 6),
Filter("","elements","product_name")
)
Rich Text Elements in Kontent can contain inline references to other content items within the project using links, inline content items, or components. To accomplish this using the Python SDK you need to:
- Create a custom resolver
- Import your custom resolver
- Register the resolver with your DeliveryClient
Once a custom resolver is implemented and registered to the client, resolution will automatically happen when a ContentItem or ContentItemListing is built.
Use samples and example.py as a reference.
By default, Kentico Kontent returns inline content item links with an empty "href" attribute and a "data-item-id" attribute containing the content item ID:
<!-- inline content item link -->
<a data-item-id=\"80c7074b-3da1-4e1d-882b-c5716ebb4d25\" href=\"\">
To resolve the link, create a CustomLinkResolver that uses the resolve_link() method to evaluate and return an href string value:
class CustomLinkResolver:
@staticmethod
def resolve_link(link):
if link.type == "coffee":
return f"/coffees/{link.url_slug}"
if link.type == "article":
return f"/articles/{link.url_slug}"
The link argument passed into resolve_link() allows you to access the link type, codename, and url_slug.
Once a custom link resolver is implemented, import it and register to your DeliveryClient:
from kontent_delivery.client import DeliveryClient
# namespace for your custom resolver will vary
from samples.custom_link_resolver import CustomLinkResolver
client = DeliveryClient("your_project_id")
client.custom_link_resolver = CustomLinkResolver()
By default, Kentico Kontent returns inline content items and components links as objects with identifying attributes like "data-type" and "data-codename." This attributes are used to identify the inline item:
<!-- Inline Content Item: -->
<object type=\"application/kenticocloud\" data-type=\"item\" data-rel=\"link\" data-codename=\"on_roasts\"></object>
<!-- Component: -->
<object type=\"application/kenticocloud\" data-type=\"item\" data-rel=\"component\" data-codename=\"n71d0469e_9a77_0168_8f49_08f6d3bcaca3\"></object>
To resolve the inline item or component, create a CustomItemResolver that uses the resolve_item() method to replace the object elements:
class CustomItemResolver:
@staticmethod
def resolve_item(linked_item):
if linked_item.content_type == "article":
return f"<h1>{linked_item.elements.title.value}</h1>"
if linked_item.content_type == "tweet":
return (f"<blockquote class='twitter-tweet' data-lang='en'"
f"data-theme={linked_item.elements.theme.value[0].codename}>"
f"<a href={linked_item.elements.tweet_link.value}></a></blockquote>")
The linked_item argument passed into resolve_item() is a ContentItem.
Once a custom link resolver is implemented, import it and register to your DeliveryClient:
from kontent_delivery.client import DeliveryClient
# namespace for your custom resolver will vary
from samples.custom_item_resolver import CustomItemResolver
client = DeliveryClient(config.project_id, options=config.delivery_options)
client.custom_item_resolver = CustomItemResolver()
When enumerating all items in the specified project, or if exceeding the item response limit on the Delivery API's items endpoint is necessary, the items-feed endpoint can be used.
Getting an item feed from Kontent can be accomplished by using the DeliveryClient's get_content_items_feed method:
r = client.get_content_items_feed()
while r.next:
next_result = r.get_next()
r.feed.items.extend(next_result.items)
for item in r.feed.items:
print(item.name)
# prints:
# Example Item Name 001
# Example Item Name 002
# ...
# Example Item Name 4300
# etc.
Using the DeliveryClient's get_content_items_feed method will produce a ContentItemsFeed object that stores ContentItemListing objects in a "feed" attribute. Paginated results can be retrieved by using the get_next method, which returns a ContentItemListing object.
Attribute | Description |
---|---|
feed | Contains ContentItemListing objects from the first page of the response, and can be extended using the get_next() method |
url | Contains the requested url. |
next | Stores the "x-continuation" header for retrieving the next page of results. Set to None when no more pages are available. |
Filtering can also be used when getting content item feeds:
r = client.get_content_items_feed(
Filter("system.codename", "[eq]", "test999")
)
print(r.url)
# prints: ~/items-feed?system.codename[eq]=test999
Getting a content type from Kontent can be accomplished by using the DeliveryClient's get_content_type method and passing in the content type's codename:
response = client.get_content_type("article")
print(response.codename)
print(response.elements.body_copy)
print(response.elements.body_copy.name)
# prints:
# article
# namespace(type='rich_text', name='Body Copy')
# Body Copy
Using this method will return a ContentType. To access elements and their values use dot notation in the format:
response.elements.element_name.attribute_name
Attribute | Description |
---|---|
id | An identifying GUID. |
name | The type's display name as seen in the Kontent UI. |
codename | A unique identifying codename set in the Kontent UI. Note: Special characters are replaced with "_". |
last_modified | The date the type was last editted in the Kontent UI. |
elements | Contains the content type elements. |
api_response | Response object from the request. |
Getting a type listing from Kontent can be accomplished by using the DeliveryClient's get_content_types method:
response = client.get_content_types()
for content_type in response.types:
print(content_type.name)
# prints:
# About us
# Accessory
# Article
# ...
Using the DeliveryClient's get_content_types method will produce a ContentTypeListing object that stores each retrieved item as a ContentType in the "types" attribute.
Attribute | Description |
---|---|
content_types | Contains the listing of project content types. |
pagination | Dictionary containing the skip, limit, count, and next page parameters. |
skip | Sets the number of objects to skip when requesting a list of objects. |
limit | Sets the number of objects to retrieve in a single request. |
count | The number of items in the response. |
next_page | Contains the next page of results. |
api_response | Response object from the request. |
Getting a taxonomy group from Kontent can be accomplished by using the DeliveryClient's get_taxonomy method and passing in the taxonomy group's codename:
response = client.get_taxonomy("personas")
print(response.codename)
print(response.terms)
print(response.terms[0].codename)
# prints:
# personas
# [namespace(name='Coffee expert', codename='coffee_expert', terms=[namespace(name='Barista', codename='barista', terms=[]) ...
# coffee_expert
Using this method will return a TaxonomyGroup. To access terms and their values use dot notation in the format:
response.terms[list_postion].attribute_name
You can get term and nested term values by looping through the terms lists or chaining together .terms[list_postion]:
for term in response.terms:
print(term.name)
# prints:
# Coffee expert
# Coffee enthusiast
# ...
print(response.terms[0].terms[0].codename)
# prints:
# barista
Attribute | Description |
---|---|
id | An identifying GUID. |
name | The type's display name as seen in the Kontent UI. |
codename | A unique identifying codename set in the Kontent UI. Note: Special characters are replaced with "_". |
last_modified | The date the type was last editted in the Kontent UI. |
terms | Taxonomy group terms containing name, codename, and nested terms. |
api_response | Response object from the request. |
Using the DeliveryClient's get_taxonomies method will produce a TaxonomyGroupListing object that stores each retrieved item as a TaxonomyGroup in the "taxonomy_groups" attribute.
Attribute | Description |
---|---|
taxonomy_groups | Contains the listing of taxonomy groups. |
pagination | Dictionary containing the skip, limit, count, and next page parameters. |
skip | Sets the number of objects to skip when requesting a list of objects. |
limit | Sets the number of objects to retrieve in a single request. |
count | The number of items in the response. |
next_page | Contains the next page of results. |
api_response | Response object from the request. |
Using the DeliveryClient's get_languages method will produce a LanguageListing object that stores each retrieved language as a Language in the "languages" attribute.
To access language values loop through the language listing and use dot notation:
response = client.get_languages()
for language in response.languages:
print(language.name)
# prints:
# English (United States)
# Spanish (Spain)
# ...
Attribute | Description |
---|---|
languages | Contains a list of languages. |
pagination | Dictionary containing the skip, limit, count, and next page parameters. |
skip | Sets the number of objects to skip when requesting a list of objects. |
limit | Sets the number of objects to retrieve in a single request. |
count | The number of languages in the response. |
next_page | Contains the next page of results. |
api_response | Response object from the request. |
Attribute | Description |
---|---|
id | An identifying GUID. |
name | The langauge's display name as seen in the Kontent UI. |
codename | A unique identifying codename set in the Kontent UI. Note: Special characters are replaced with "_". |
api_response | Response object from the request. |
When you've obtained the URL for an asset, you can use the Kontent Image Transformation API to make on-the-fly modifications to the image.
To use this, create an instance of ImageBuilder and pass the desired transformation methods into the bulder's .transform() method. Example:
from kontent_delivery.builders.image_builder import ImageBuilder
# Get content item
response = client.get_content_item("coffee_processing_techniques")
# Get asset url
asset_url = response.elements.teaser_image.value[0].url
# Transform image
image = ImageBuilder(asset_url)
transformed_image = image.transform(
image.width(300),
image.height(300),
image.pixel_ratio(1.5),
)
print(transformed_image)
# prints: ~/coffee-processing-techniques-1080px.jpg?w=300&h=300&dpr=1.5
Method | Accepted Values | Example |
---|---|---|
.width | positive integer, or float between 0 and 1 | .width(300) |
.height | positive integer, or float between 0 and 1 | .height(300) |
.pixel_ratio | float greater than 0 but less than 5 | .pixel_ratio(1.5) |
.fit_mode | string with value "crop", "clip", or "scale" | .fit_mode("crop") |
.rect | 4 integer values representing pixels or floats representing percentages | .rect(100,100,0.7,0.7) |
.focal_point | 2 floats between 0 and 1 and one integer between 1 and 100 | .focal_point(0.2,0.7,5) |
.background_color | string containing 3, 4, 6, or 8 characters | .background_color("7A0099EE") |
.output_format | string with value in this supported list | .output_format("webp") |
.quality | integer between 1 to 100 | .quality(85) |
.lossless | True, False, 0, or 1 | .lossless(True) |
.auto_format_selection | True, False, 0, or 1 | .auto_format_selection(False) |