Migration of Categories (Collections) using Shopify API

In this post, we'll use Shopify Admin API to create collections in our store. Before we go on, you are going to need a development store and create a private app where it provides API credentials to interact with store data.

Migration of Categories (Collections) using Shopify API
Migration of Categories (Collections) using Shopify API

In this post, we'll use Shopify Admin API to create collections in our store. Before we go on, you are going to need a development store and create a private app where it provides API credentials to interact with store data.

I highly recommend watching the video below to set up a development store in Shopify:

If you already have one, then let's start migrating categories to our Shopify store.

Building Shopify Client

In this part, we are going to build client to interact with Shopify API by sending requests and receiving responses. Simply, we are creating a separate class where it holds all request-response logic and related attributes.  That'll make our project more clean and maintainable in the future.

Create an empty directory named client and inside it add __init__.py the file which makes it a python package. Next, create another file named shopify_client.py and start by adding a class named ShopifyClient:

client/shopify_client.py

import json
import requests

class ShopifyClient:

    def __init__(self, api_password: str, shop_url: str, endpoint: str):
        self.api_password = api_password
        self.shop_url = shop_url
        self.endpoint = endpoint
        self.headers = {
            "Accept": 'application/json',
            "Content-Type": 'application/json'
        }

The constructor includes the main properties of the client that will be used across functions to interact with Shopify API. For each request, we need the  following properties:

api_password - Password that is given in the private app of Shopify Dev Store.

shop_url - Shop URL of development store.

endpoint - Endpoint that will be used to send requests for.

headers -  Header rules to include for each request.

Shopify requires an authorization header, so it knows which particular shop data it should serve. At this point, we'll set only API password as value to header named X-Shopify-Access-Token. Let's continue by creating a new function named api_request that will handle all types of requests:

client/shopify_client.py

    def api_request(self, *args, **kwargs):
        self.headers.update({
          "X-Shopify-Access-Token": self.api_password
        })

        context = {
            "method": kwargs.get("method"),
            "url": f"{self.shop_url.rstrip('/')}{self.endpoint}",
            "timeout": 60,
            "headers": self.headers
        }

        if kwargs.get("method") == "POST":
            context["data"] = json.dumps(kwargs.get("data"))
        
        return requests.request(**context).json()

Simply, we're updating the headers to include the authorization header as well. Now, let's see what's inside context:.

method - It defines the request method type such as GET, POST or DELETE and takes the value from kwargs.

url - The shop_url concatenated with endpoint to produce an absolute URL where the requests will be sent.

timeout - Maximum time of waiting for a response.

After that, we're checking if the method is POST then insert data inside context as well. Once the context is built the request() function will send it and return a response as a json.

Adding Data Models

Now, we need a dummy dataset where it'll migrate to Shopify. To achieve that, we can use factory_boy package to produce fake data based on the specific model.  Create a directory named data and also include __init__.py file inside it. Let's create the base structure of our model by using dataclass:

data/entities.py

from dataclasses import dataclass

@dataclass
class Category:
    title: str
    body_html: str
    published: bool
    sort_order: str
    image: str

Where all these attributes are coming from? The attributes represent required fields of Shopify Collections API.

Shopify Collection API example

Currently, we only need the fields above to create a Shopify Collection. The list of belonged products will remain empty for now since we don't have one. Now, it's time to create factories using factory_boy to produce some data to migrate it later

data/factories.py

import factory
import factory.fuzzy

from data.entities import Category

class CategoryFactory(factory.Factory):

    class Meta:
        model = Category

    title = factory.Faker('name')
    body_html = factory.Faker('sentence')
    published = factory.fuzzy.FuzzyChoice([True, True, True, False])
    sort_order = "manual"
    image = factory.Faker('image_url')

This factory above creates instances of Category model with fake data based on the attributes we provide by using Faker. In other words, we are mocking the Category class to test our functionalities.

Migration of Categories

So, we finished setting up the client and produce some fake data to migrate it later. Now, it's to start creating the base logic which will put everything together and migrate all data to Shopify. Create another file named category.py in the root level of your directory:

category.py

import logging

from client.shopify_client import ShopifyClient
from data.factories import CategoryFactory

class CategoryMigration:

    def __init__(self):
        self.log = logging.getLogger(__name__)
        logging.basicConfig(level = logging.INFO)

        self.client = ShopifyClient(
            api_password="YOUR_API_PASSWORD",
            shop_url="YOUR_SHOP_URL",
            endpoint=ShopifyStoreAdmin.COLLECTION_ENDPOINT.value
        )

We are going to use logging module to print data in the console to see the process in real-time. Also, initializing the Shopify client to make it ready for sending requests.

Next, let's use the factory to produce a list of instances with fake data:

    def generate_categories(self):
        category_data = CategoryFactory.create_batch(5)

        collections_shopify = []

        for category in category_data:
            collection_row = {
                "custom_collection" : {
                    "title": category.title,
                    "body_html": category.body_html,
                    "published": category.published,
                    "sort_order": category.sort_order,
                    "image": {"src": category.image }
                }
            }
            collections_shopify.append(collection_row)
        return collections_shopify

As you see, we are building the object structure as Shopify requires and appending it into the list. The python dictionary will be converted to actual json in our client whenever request prepared to be sent.

    def migrate(self):
        collections_shopify = self.generate_categories()
        for collect in collections_shopify:
            res = self.client.api_request(
                method="POST",
                data=collect
            )
            self.log.info("Response %s", res)


categories_migration = CategoryMigration()
categories_migration.migrate()

Lastly, iterate through generated data and send them to Shopify by using POST the method. The logs will let us know what's going on behind the scenes.

Great! Now you can run the file and watch the migration of data to your development store.

python category.py

The source code is available on Github.

Video Explanation with more details

Support 🌏

If you feel like you unlocked new skills, please share them with your friends and subscribe to the youtube channel to not miss any valuable information.

Reference

Thumbnail Icons made by Freepik from www.flaticon.com