Skip to content

Instantly share code, notes, and snippets.

@lagivan
Forked from sreesarma/postman2confluence.py
Last active March 5, 2025 01:12
Show Gist options
  • Save lagivan/d2650e38a20c790b9126ba202de04164 to your computer and use it in GitHub Desktop.
Save lagivan/d2650e38a20c790b9126ba202de04164 to your computer and use it in GitHub Desktop.
Generate Wiki-formatted API Documentation from Postman Collections
# Run with full path to postman collection file as a single parameter, in Postman 2.1 format
# See "Postman to Wiki" at https://medium.com/@sreedharc/postman-to-wiki-e7d31c76db57 for more information
# Generates Markdown for Confluence Wiki from a given Collection file exported from Postman per Collections 2.1 schema
import json
import sys
import os
import re
from operator import itemgetter
from itertools import groupby
input_file = sys.argv[1]
working_dir = os.path.dirname(input_file)
serial_num = 1
output_dir = "generated"
def clean(s):
s = (str(s).decode('string_escape'))
s = s.replace('{{', '$').replace('}}', '').replace('\r\n', '')
return s[1:-1]
def clean_list(a_list):
return [clean(i) for i in str(a_list).split(',')]
def escape_markup(value):
return re.sub(r"([{\}\[\]])", r"\\\1", value)
def encode_full_url(url):
return (
re.sub(r"\s+", " ", url).replace("{{", "$").replace("}}", "")
.replace("%", "%25").replace("'", "%27")
.replace("{", "%7B").replace("}", "%7D")
.replace("\"", "%22").replace(" ", "%20")
.replace("[", "%5B").replace("]", "%5D")
)
def format_raw_data(data):
try:
return 'javascript', json.dumps(json.loads(data, 'ascii'), indent=2)
except:
return 'xml', data
# Generates markdown for the section describing Collection Variables
def gen_variables_section(list_of_variables):
op = "h2.Variables Used in this Collection" + "\n"
op += "||Name||Description||Example||" + "\n"
for variable in sorted(list_of_variables, key=itemgetter('key')):
op += "|" + variable["key"] + "| |" + variable["value"] + "|" + "\n"
return op
# Generates markdown for request section
def gen_request_section(item):
item = json.loads(json.dumps(item))
# Heading, e.g. GET Users
request_section = "h2. *" + item["request"]["method"] + "* " + item["name"] + "\n"
# Description
if "description" in item["request"]:
request_section += item["request"]["description"] + "\n"
request_section += "\nh3. Request" + "\n"
# URL format
request_section += "h4. Request URL" + "\n"
request_section += "{panel}" + encode_full_url(item["request"]["url"]["raw"]) + "\n{panel}" + "\n"
# Headers
# print("# Headers: " + len(item["request"]["header"]))
request_section += "h4. Request Headers" + "\n"
request_section += "||Key||Value||Description||" + "\n"
for header in item["request"]["header"]:
description = header["description"] if "description" in header else " "
request_section += "|" + header["key"] + "|" + escape_markup(header["value"]) + "|" + description + "|" + "\n"
# Request Parameters
add_param_section = False
param_section = "\nh4. Request Parameters" + "\n"
param_section += "||Parameter Type||Key||Value||Description||" + "\n"
url = item["request"]["url"]
# Path Parameters
if "path" in url:
path_parameters = url["path"]
for param in path_parameters:
if "{{" in param:
add_param_section = True
param_section += "|" + "Path Parameter" + "|" + param.replace("{{", "$").replace("}}", "") \
+ "|" + " " + "|" + " " + "|" + "\n"
# Query String Parameters
if "query" in url:
qs_parameters = url["query"]
for param in qs_parameters:
add_param_section = True
description = param["description"] if "description" in param else " "
param_section += "|" + "Query String Parameter" + "|" + param["key"] \
+ "|" + escape_markup(param["value"]) + "|" + description + "|" + "\n"
print(param_section)
if add_param_section:
print("ADD PARAMETERS SECTION")
request_section += param_section
if str(item["request"]["method"]) != "GET":
request_section += "\nh4. Request Payload" + "\n"
language = "javascript"
# print(item["request"]["body"])
if (not("body" in item["request"]) or item["request"]["body"] is None):
# print("No Payload")
payload = "{}"
else:
# print("CLEANED UP")
if "raw" in item["request"]["body"]:
p = json.dumps(item["request"]["body"]["raw"])
x = clean(json.dumps(json.loads(p)))
# print(x)
if not x:
payload = "{}"
else:
language, data = format_raw_data(x)
payload = data
else:
payload = "{}"
# print("PAYLOAD: \n" + payload)
# Request payload as code snippet
request_section += "{code:title=Payload|theme=FadeToGrey|linenumbers=true|language=" + language \
+ "|firstline=0001|collapse=true}" + payload + "{code}"
else:
print("No payload for GET")
return request_section
# Generates markdown for response section
def gen_response_section(item):
response = json.loads(json.dumps(item["response"]))
response_section = "\nh3. Response " + "\n"
# print(item["name"] + " - " + str(len(response)))
if len(response) > 0:
code = "200*"
if "code" in response[0]:
code = str(response[0]["code"])
else:
print(response[0])
status = "OK*"
if "status" in response[0]:
status = str(response[0]["status"])
else:
print(response[0])
language = "javascript"
if "body" in response[0]:
body = response[0]["body"]
if body is None:
body = "{}"
# print("BODY: \n" + body)
language, body = format_raw_data(body)
else:
body = " "
print("Warning - response body is missing")
# Response body as code snippet, response code + status as snippet title
response_section += "{code:title=" + code + " " + status + "|theme=FadeToGrey|linenumbers=true|language=" \
+ language + "|firstline=0001|collapse=true}" + body + "{code}"
return response_section
# Generates a section for the given resource
def gen_resource_section(name, group):
global serial_num
global working_dir
global output_dir
resource_sections = ""
if not os.path.exists(output_dir):
os.mkdir(output_dir)
for item in group:
print("\t Processing " + str(item["request"]["method"]))
resource_section = gen_request_section(item) + gen_response_section(item) + "\n"
# Write to a separate output file
file_name = str(serial_num) + "." + name + "-" + str(item["request"]["method"]) + ".txt"
with open(os.path.join(working_dir, output_dir, file_name), 'w') as output_file:
output_file.write(resource_section)
serial_num = serial_num + 1
resource_sections += resource_section
return resource_sections
# Read in the input as JSON
with open(input_file) as json_data:
input_data = json.load(json_data)
# Extract all variables and print them out as a table
v = gen_variables_section(input_data["variable"])
# print(v)
# Now extract all requests ("items" in the collection) sorted by resource name ("name" in the collection)...
items = sorted(input_data["item"], key=lambda x: (x['name'], x['request']['method']))
# print("Number of items: " + str(len(items)))
# ... and generate documentation for supported methods by resource, in alphabetical order (GET, PATCH, POST, etc.)
resource_full = ""
for name, group in groupby(items, key=lambda y: (y['name'])):
print("Exporting: " + name)
resource_full += gen_resource_section(name, group)
with open(os.path.join(working_dir, output_dir, "full.txt"), 'w') as output_file:
output_file.write(resource_full)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment