Unicat API Tutorials

Up - Home


Generate a product XML feed

Note: we'll be using our home-built API connector Create an API connector. We could simplify this by using the 'official' Unicat client lib, but that wouldn't give the API insights because it is more high-level.

Note: at the end of the page, we show the same example while using the Unicat client lib.

We'll start by fetching the root record, and then walk through the entire records tree depth-first. For every product, we emit the record so we can use the data.

We set the channel to 'Main channel' and the ordering to 'Main ordering'. We need to set the keys for these, they are found in the project options.

To keep thing simple, we assume every call is successful.

from ccapi import UnicatApi
from .config import PROJECT_GID, SECRET_API_KEY

ccapi = UnicatApi("https://unicat.app", PROJECT_GID)
success, result = ccapi.connect(SECRET_API_KEY)

project = ccapi.data["cc.projects"][PROJECT_GID]
channels_by_name = {v: k for k, v in project["options"]["channels"].items()}
orderings_by_name = {v: k for k, v in project["options"]["orderings"].items()}

language = en
channel = channels_by_name["Main channel"]
ordering = orderings_by_name["Main ordering"]

def walk_tree_depth_first(ccapi, parent_gid, language, channel, ordering):
    top = 0
    size = 100
    while True:
        data = {
            "record": parent_gid,
            "language": language,
            "channel": channel,
            "ordering": ordering,
            "page.top": top,
            "page.size": size,
        }
        succes, result = ccapi.call("/records/children", data)
        if not result["children"]:
            return
        for record_gid in result["children"]:
            record = ccapi.data["records"][record_gid]
            yield record
            if record["childcount"] > 0:
                yield from walk_tree_depth_first(ccapi, record_gid, language, channel, ordering)
        top += size
        if top >= result["children.size"]:  # know when to stop
            break

succes, result = ccapi.call("/records/root", { "language": "en" })
root_record = ccapi.data["records"][result["root"]]

This sets up the tools we need. We use pagination in case we have more than one hundred children anywhere in the tree, and we pre-check the childcount so we don't perform calls we know won't return anything (they can still return an empty list, since childcount doesn't take the channel into account).

When we walk the tree, we only want to process records that have the article class, so we know that the fields artnr, price, and stock are available (your definitions and classes may differ, but you can extrapolate from here).

article_class_gid = None
for class_gid, class_ in ccapi.data["classes"].items():
    if class_["name"] == "article":
        article_class_gid = class_gid

with open("product-feed.xml", "w") as f:
    f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    f.write('<products>\n')
    for record in walk_tree_depth_first(ccapi, root_record["gid"], language, channel, ordering):
        definition = ccapi.data["definitions"][record["definition"]]
        if article_class_gid not in definition["classes"]:
            continue
        recordfields = record["fields"][language]
        artnr = recordfields["artnr"]
        price = (
            str(recordfields["price"]) if recordfields["price"] else "0.00"
        )
        stock = str(recordfields["stock"])
        f.write(f' <product artnr="{artnr}" price="{price}" stock="{stock}"/>\n')
    f.write('</products>\n')

Add an image url for each product

In the inner loop, we should use the image field to fetch a public url for a thumbnail.

        succes, result = ccapi.call(
            "/assets/get", {"asset": recordfields["image"]}
        )
        if success:
            image = ccapi.data["assets"][result["asset"]]
            options = {
                "fill": "200,200",
                "type": "jpg",
                "optimize": True,
            }
            succes, result = ccapi.transform(image, options)
            download_url = result["public_url"]

We can add that to the written XML file.

Using the Unicat client lib

pip install unicat

Ok, here it goes:

from unicat import Unicat, UnicatTransform
from .config import PROJECT_GID, SECRET_API_KEY, LOCAL_ASSET_FOLDER

unicat = Unicat("https://unicat.app", PROJECT_GID, SECRET_API_KEY, LOCAL_ASSET_FOLDER)
if not unicat.connect():
    raise Exception("Invalid connection settings")

language = unicat.project.default_language
channel = unicat.project.channels["Main channel"]
ordering = unicat.project.orderings["Main ordering"]
transform = UnicatTransform(resize="fill", width=200, height=200, type="jpg", optimize=True)

with open("product-feed.xml", "w") as f:
    f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
    f.write('<products>\n')
    for record in unicat.walk_record_tree(channel=channel, ordering=ordering):
        if "article" not in [class_.name for class_ in record.definition.classes]:
            continue
        fields = record.fields[language]
        artnr = fields["artnr"].value
        price = (
            str(fields["price"].value) if fields["price"].value else "0.00"
        )
        stock = str(fields["stock"].value)
        image_url = fields["image"].publish_transformed(transform)
        f.write(f' <product artnr="{artnr}" price="{price}" stock="{stock}" image="{image_url}"/>\n')
    f.write('</products>\n')