Was this page helpful?
Caution
You're viewing documentation for an unstable version of ScyllaDB Sphinx Theme. Switch to the latest stable version.
REST API (Redocly)¶
The ScyllaDB Cloud documentation uses Redocly to render OpenAPI/Swagger specifications as interactive API documentation.
Note
Currently, only ScyllaDB Cloud Docs uses REST API documentation with Redocly.
Interested in making this a reusable extension? Let us know in this issue #722.
How it works¶
The extension:
Downloads the OpenAPI specification from the configured URL
Generates code snippets (e.g., curl examples) using
snippet-enricher-cliSaves the enriched spec to
_static/swagger.jsonRenders the API documentation using Redocly at
/api.html
Prerequisites¶
Install the required dependencies:
poetry add requests
npm install -g snippet-enricher-cli
Configuration¶
Add to conf.py¶
Configure the OpenAPI extension and specify the spec URL:
extensions = [
# ... other extensions
"scylladb_openapi"
]
# Additional pages
html_additional_pages = {'api': 'api.html'}
# Additional static paths
html_static_path = ['openapi']
# Custom CSS
html_css_files = [
'custom.css',
]
# OpenAPI configuration
scylladb_openapi_url = 'https://api.cloud.scylladb.com/api-docs.json'
scylladb_openapi_server_url = 'https://api.cloud.scylladb.com'
scylladb_openapi_output_file = '/_static/swagger.json'
Create the extension¶
Create _ext/scylladb_openapi.py:
# -*- coding: utf-8 -*-
import json
import requests
import subprocess
def build_openapi_spec(app, exception) -> None:
config = app.config
output_file = app.outdir + config.scylladb_openapi_output_file
spec_url = config.scylladb_openapi_url
server_url = config.scylladb_openapi_server_url
download_openapi_spec(output_file, spec_url, server_url)
generate_code_snippets(output_file)
def download_openapi_spec(output_file, spec_url, server_url) -> None:
print("Downloading OpenAPI spec from: " + spec_url)
response = requests.get(spec_url, allow_redirects=True)
content = json.loads(response.text)
# Set the production URL
content["servers"][0]["url"] = server_url
with open(output_file, "w+") as file:
json.dump(content, file)
print("OpenAPI document downloaded to: " + output_file)
def generate_code_snippets(output_file) -> None:
cmd = f"snippet-enricher-cli --targets='shell_curl' --input={output_file}"
process = subprocess.Popen(cmd, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = process.communicate()
errcode = process.returncode
if errcode != 0:
raise Exception("Error generating code snippets: " + str(err))
# Decode URL-encoded placeholders
modified_content = out.decode().replace('%7B', '{').replace('%7D', '}')
with open(output_file, "w+") as file:
file.write(modified_content)
print("Code snippets generated to: " + output_file)
def setup(app):
app.connect('build-finished', build_openapi_spec)
app.add_config_value(
"scylladb_openapi_url",
default="https://api.cloud.scylladb.com/api-docs.json",
rebuild="html",
)
app.add_config_value(
"scylladb_openapi_server_url",
default="https://api.cloud.scylladb.com",
rebuild="html",
)
app.add_config_value(
"scylladb_openapi_output_file",
default="/_static/swagger.json",
rebuild="html",
)
Create the template¶
Create _templates/api.html:
{% extends "!layout.html" %}
{% block title %} API Reference | Scylla Docs {% endblock %}
{% set openapi_path = pathto('_static/swagger.json', 1) %}
{% block scripts %}
<script type="text/javascript" src="{{ pathto('_static/js/runtime.bundle.js', 1) }}"></script>
<script type="text/javascript" src="{{ pathto('_static/js/main.bundle.js', 1) }}"></script>
{% include 'scylladb-scripts.html' %}
{% include 'local-scripts.html' %}
{% endblock %}
{% set hide_pre_content = "true" %}
{% set hide_post_content = "true" %}
{% set hide_sidebar = "true" %}
{% set hide_secondary_sidebar = "true" %}
{% set full_width = "true" %}
{% set body ="<div id='redoc'></div>
<script src='https://cdn.redoc.ly/redoc/v2.1.3/bundles/redoc.standalone.js'></script>
<script>
function getCssVariableValue(variable) {
return getComputedStyle(document.documentElement)
.getPropertyValue(variable).trim();
}
function calculateScrollOffset(element1Selector, element2Selector) {
const element1 = document.querySelector(element1Selector);
const element2 = document.querySelector(element2Selector);
const element1Height = element1 ? element1.offsetHeight : 0;
const element2Height = element2 ? element2.offsetHeight : 0;
return element1Height + element2Height;
}
function initializeRedoc() {
const textColor = getCssVariableValue('--text-color');
const primaryColor = getCssVariableValue('--primary-color');
const linkColor = getCssVariableValue('--link-color');
const sidebarBackgroundColor = getCssVariableValue('--navigation-bg');
const borderColor = getCssVariableValue('--border-color');
const scrollOffset = calculateScrollOffset('.header', '.promo-banner');
Redoc.init('" + openapi_path + "', {
scrollYOffset: scrollOffset,
hideDownloadButton: true,
theme: {
sidebar: {
backgroundColor: sidebarBackgroundColor,
width: '357px',
textColor: textColor,
},
colors: {
primary: { main: textColor },
},
typography: {
fontFamily: 'Roboto, sans-serif',
fontSize: '16px',
},
},
}, document.getElementById('redoc'));
}
document.addEventListener('DOMContentLoaded', function() {
document.documentElement.classList.add('openapi-page');
initializeRedoc();
});
</script>
" %}
Custom styling¶
Create _static/custom.css to style Redocly:
/* Reset scroll padding */
.openapi-page {
scroll-padding-top: 0 !important;
}
.openapi-page .content-body {
padding: 0;
}
/* Sidebar styling */
#redoc .menu-content .scrollbar-container > ul {
padding: 30px 40px;
}
#redoc .menu-content ul ul {
border-left: 1px solid var(--link-color);
margin-left: 20px;
}
#redoc .menu-content label.active,
#redoc .menu-content label:hover {
color: var(--link-color);
background: transparent;
}
/* Right panel */
#redoc .http-verb {
border-radius: 4px;
}
#redoc code {
color: inherit;
background: transparent;
}