<-

As mentioned in the Projects section, this website it’s a Flask (Python framework) web application and, initially, the blog section was a bunch of rendered .md (Markdown) files. This solution worked well, however, since the .md files were being stored in the application’s folder, every time I’ve created a new blog post, I’d have to rebuild the entire application. This was not a very practical setup and so I’ve started looking for alternatives.

As an aspiring web developer, I often listen to the Syntax podcast and Sanity is a regular sponsor of their shows. One day, they had the guys from Sanity in one of their Supper Club episodes and I though “hmm, could be a nice alternative for the setup that I currently have”. And so, I’ve decided to give it a try…

Ok but… What’s Sanity.io?

Sanity, as defined on Jamstack’s website, is “the platform for structured content”. It’s a powerful CMS built in JavaScript, highly customizable and with a real-time hosted data store.

Here’s the documentation on how to get started with Sanity.

Flask and Sanity integration

In this article, I’ll show you a similar setup to the one that I currently have on my personal website.

First, this is a the file structure that I’ve used:

rubenoliveira-tech/ 
app/
func_posts.py
routes.py
configs/
configs.ini
app.py
.env

01. Installing requirements

# Library to load the env vars 
pip install python-dotenv

# Library to render text to HTML
pip install portabletext-html

02. Configs and environment variables

configs.ini

In this file, I’ve stored the Sanity API and CDN URL.

[sanity] 
URL = <https://<ID>.api.sanity.io/>
CDN = <https://cdn.sanity.io/images/<ID>/production/>

.env

For the Sanity API token, I’ve set it as a env var.

CMS_TOKEN=<API_TOKEN>

03. Loading the environment variables

Inside the app.py file, we need to import the dotenv library and than load the variables.

from dotenv 
import load_dotenv load_dotenv()

04. Working with Sanity API

In order to interact with the Sanity API, I’ve created a specific file called func_posts.py. Here, I’ve created 2 functions: get_all_posts() and get_post().

But first, we need to load the configuration settings and setup the authentication headers:

# Import modules 
import requests
import configparser
from portabletext_html import PortableTextRenderer

# Set auth headers
CMS_TOKEN = "Bearer " + str(os.getenv("CMS_TOKEN"))
headers = {"Authorization": CMS_TOKEN}

# Setup configuration file
config = configparser.ConfigParser()
config.read('configs/configs.ini')
SANITY_URL = config['sanity']['URL']

get_all_posts()

This function gets all posts and compiles a list with: post id, post title and post creation date. This list is used to populate the POSTS page.

# Get all posts 
def get_all_posts():

endpoint = "v2021-10-21/data/query/production?query="
# GROQ query to get all posts
query = "*[_type == 'post'] | {_id, title, _createdAt}"
data = requests.get(f"{SANITY_URL}{endpoint}{query}", headers = headers)
all_posts = data.json()
all_posts_list = [] # List to be sent to FE

# Loop through the fetched data and populate the posts list
for post in range(len(all_posts['result'])):
post_id = all_posts['result'][post]['_id']
post_title = all_posts['result'][post]['title']
post_created_at = time_formatter(all_posts['result'][post]['_createdAt'])
all_posts_list += [{'id': post_id, 'title': post_title, 'createdAt': post_created_at}]

return all_posts_list

get_post()

This function gets the post content by ID. This is used to populate the POST page.

# Get post by ID 

def get_post(post_id):

endpoint = "v2021-10-21/data/query/production?query="
query = f"*[_id == '{post_id}']" # GROQ query to get post by ID
data = requests.get(f"{SANITY_URL}{endpoint}{query}", headers = headers)
post = data.json() block = 0 # Aux var for the 'for loop'
post_content = "" # Post content to be sent to FE

# Loop through the post and renders the content to be sent to the FE
for block in range(len(post['result'][0]['body'])):
if 'children' in post['result'][0]['body'][block]: # Text
renderer = PortableTextRenderer({
'_key': post['result'][0]['body'][block]['_key'],
'_type': post['result'][0]['body'][block]['_type'],
'children': post['result'][0]['body'][block]['children'],
'markDefs': post['result'][0]['body'][block]['markDefs'],
'style': post['result'][0]['body'][block]['style']
})
post_content += renderer.render()
if 'asset' in post['result'][0]['body'][block]: # Images
image_ref = post['result'][0]['body'][block]['asset']['_ref']
image_url = create_img_url(image_ref)
post_content += f'<img src = "{image_url}">'
block += 1

# Update pre to keep code format
post_content_fmt = post_content.replace('pre', 'pre')

return post_content_fmt

Additionally, I’ve created this extra function to build the URL for the images added to the posts.

# Create image URL 
def create_img_url(ref):

cdn_url = config['sanity']['CDN']
image_ref_temp = ref.replace("image-", "") # Remove the 'image-'
# Replace the '-ext' by '.ext'
image_ref = image_ref_temp[:48] + image_ref_temp.replace(image_ref_temp[:49], ".")
image_url = cdn_url + image_ref

return image_url

And that’s it! I hope you find this post useful and feel free to reach me out through my social platforms if you’ve any doubt 🙂

See you on the next one!