Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options --combine, --exclude, refactor a bit #20

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
build/
dist/
dist/
*.pyc
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ Feel free to submit a pull request which fixes this.

pip install apiary2postman

### Or, run from your checkout

git clone <repo-url>
cd apiary2postman/apiary2postman
./apiary2postman.py <args>

# Usage

apiary2postman json blueprint.json --output postman.json
Expand All @@ -47,13 +53,25 @@ Feel free to submit a pull request which fixes this.

cat some.blueprint | apiary2postman blueprint > postman.dump

##### To generate a total Postman environment dump from Apiary API, use the `api` subcommand:
##### To generate a total Postman environment dump from Apiary API, use the `api` subcommand with your Apiary API name:

apiary2postman api my_api > postman.dump
apiary2postman api my_api > my_api.dump

##### To ignore certain Apiary API resource groups, use `--exclude` (case-insensitive substring match):

apiary2postman --exclude 'IGNORE ME' --exclude 'ME TOO' api my_api > my_api.dump

##### By default, `apiary2postman` creates one collection per resource group. `--one-collection` allows you to specify the name of a container collection. Folders are created w/in this collection using the resource group names, and requests are added to the folders. Caveat: resources - that would otherwise determine the folder names - are ignored.

apiary2postman --combine 'My Combined API' api my_api > my_api.dump

##### If you don't have an API key, log in to [your Apiary account](https://apiary.io), go to Settings, scroll down to Tokens. Generate one if needed, and set the environment variable `APIARY_API_KEY` to that hex string.

APIARY_API_KEY=ffffffffffffffffffffffffffffffff apiary2postman api my_api > my_api.dump

###### Or to generate only a Postman collection from Apiary API:

apiary2postman --only-collection api my_api > postman.collection
apiary2postman --only-collection api my_api > my_api.collection

It's also possible to specify the output file using the `--output`.

Expand Down
28 changes: 26 additions & 2 deletions apiary2postman/apiary2postman.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env python
from sys import stdin, stderr, stdout, argv, exit
import argparse
import json
import subprocess
import os
import platform
from converter import write
import converter
from blueprint import blueprint2json,fetch_blueprint

class bcolors:
Expand Down Expand Up @@ -77,6 +78,10 @@ def main():
help='the name of the api on apiary. I.e. testapi311 for http://docs.testapi311.apiary.io/')
parser_json.add_argument('input', metavar='input', type=file, nargs='?', default=stdin,
help='input file, formatted as JSON. If not supplied, stdin is used.')
parser.add_argument('--combine', dest='combine',
help='combine all collections into a a single top-level collection')
parser.add_argument('--exclude', dest='exclude', nargs='+',
help='exclude collections containing the provided (case-insensitive) substrings')
parser_blueprint.add_argument('blueprint_input', metavar='input', type=file, nargs='?', default=stdin,
help='input file, formatted as Blueprint API Markup. If not supplied, stdin is used.')
parser.add_argument('--output', metavar='output', type=argparse.FileType('w'), nargs=1, default=stdout,
Expand Down Expand Up @@ -108,12 +113,31 @@ def main():

blueprint = fetch_blueprint(args.name[0], apikey)
input = blueprint2json(blueprint)


output = args.output
if args.output != stdout:
output = output[0]

write(input, output, args.only_collection, args.pretty)
# unmarshal from json string
json_obj = json.loads(input)

if args.only_collection:
# return only the first collection
json_obj = converter.first_collection(json_obj)
else:
json_obj = converter.full_response(json_obj)

# exclude collections by name
if args.exclude != None and len(args.exclude) > 0:
converter.filter_collections(json_obj, args.exclude)

# combine all collections into a top-level one, removing existing folders
if args.combine != '':
converter.combine_collections(json_obj, args.combine)

# write json object out to configured destination, perhaps with whitespace
converter.write(json_obj, out=output, pretty=args.pretty)

if __name__ =='__main__':
main()
Expand Down
Binary file removed apiary2postman/blueprint.pyc
Binary file not shown.
119 changes: 86 additions & 33 deletions apiary2postman/converter.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import json
from sys import stdout
from sys import stdout, stderr
from uuid import uuid4
from time import time
import urllib

def _buildCollectionResponse(apiary):
environment = createEnvironment(apiary)
from urimagic import URITemplate


def first_collection(json_obj):
environment = createEnvironment(json_obj)

# Create the collection
collections = parseResourceGroups(
apiary['resourceGroups'],
json_obj['resourceGroups'],
environment['values'],
True)

result = {
'id' : str(uuid4()),
'name' : apiary['name'],
'description' : apiary['description'],
'name' : json_obj['name'],
'description' : json_obj['description'],
'timestamp' : int(time()),
'remote_id' : 0,
'synced' : False,
Expand All @@ -31,9 +35,9 @@ def _buildCollectionResponse(apiary):

return result

def _buildFullResponse(apiary):
def full_response(json_obj):
# Create the Environment
environment = createEnvironment(apiary)
environment = createEnvironment(json_obj)

# Create the Header
result = {
Expand All @@ -45,24 +49,76 @@ def _buildFullResponse(apiary):

# Create the collection
result['collections'] = parseResourceGroups(
apiary['resourceGroups'],
json_obj['resourceGroups'],
result['environments'][0]['values'],
False)

return result

def write(json_data, out=stdout, only_collection=False, pretty=False):
json_obj = json.loads(json_data)

if only_collection:
result_out = _buildCollectionResponse(json_obj)
else:
result_out = _buildFullResponse(json_obj)
def filter_collections(obj, exclude):
new = []
exclude = [x.lower() for x in exclude]
for key in obj.iterkeys():
if key == 'collections':
for coll in obj[key]:
append = True
for ex in exclude:
if ex in coll['name'].lower():
append = False
if append:
new.append(coll)
obj[key] = new
break

def combine_collections(obj, name):
# One top-level collection
# Each previously top-level collection becomes a folder
# The folders are discarded, after linking their requests into the new folder
for key in obj.iterkeys():
if key == 'collections':
obj[key] = [_reorgCollections(obj[key], name)]

def _folderFromCollection(c):
return {
'name': c['name'],
'id': str(uuid4()),
'description': c['description'],
'order': [],
'collection_id': c['id'],
'collection_name': c['name'],
}

def _reorgCollections(colls, name):
top = {
'name': name,
'id': str(uuid4()),
'folders': [],
'requests': [],
'description': '',
'timestamp': 0,
'remote_id': 0,
'order': [],
'synced': False,
}
for c in colls:
f = _folderFromCollection(c)
f['collection_id'] = top['id']
f['collection_name'] = top['name']
top['folders'].append(f)
top['order'].append(f['id'])
for r in c['requests']:
# add requests to folder where r['collection_id'] == c['id']
r['collectionId'] = top['id']
r['folder'] = f['id']
f['order'].append(r['id'])
top['requests'].append(r)
return top

def write(json_data, out=stdout, pretty=False):
if pretty:
json.dump(result_out, out, indent=2, separators=(',', ': '))
json.dump(json_data, out, indent=2, separators=(',', ': '))
else:
json.dump(result_out, out)
json.dump(json_data, out)


def createEnvironment(json_obj):
Expand Down Expand Up @@ -111,7 +167,7 @@ def parseResourceGroups(resourceGroups, environment_vals, only_collection):
folder['collection_id'] = collection['id']
folder['collection_name'] = collection['name']

sub_url = resource['uriTemplate']
sub_url = URITemplate(resource['uriTemplate'])
for action in resource['actions']:
request = dict()
request['id'] = str(uuid4())
Expand All @@ -122,11 +178,13 @@ def parseResourceGroups(resourceGroups, environment_vals, only_collection):
request['descriptionFormat'] = 'html'
request['method'] = action['method']

request['url'] = "{{HOST}}"+sub_url
params = {p['name']: p['example'] for p in action['parameters']}
sub_url_str = urllib.unquote(sub_url.expand(**params).string).encode('utf8')
request['url'] = "{{HOST}}"+sub_url_str
if only_collection:
for value in environment_vals:
if value['name'] == 'HOST':
request['url'] = value['value'] + sub_url
request['url'] = value['value'] + sub_url_str

request['dataMode'] = 'params'
request['data'] = []
Expand All @@ -138,29 +196,24 @@ def parseResourceGroups(resourceGroups, environment_vals, only_collection):
request['responses'] = []
request['synced'] = False

for parameter in resource['parameters']:
request['url'] = request['url'].replace('{'+ parameter['name'] +'}', parameter['example'])

headers = []
headers = {}

for example in action['examples']:
# Add Headers
for request_ex in example['requests']:
for header in request_ex['headers']:
headers.append(header['name'] + ": " + header['value'])
headers.update({h['name']: h['value'] for h in request_ex['headers']})

if len(request_ex['body']) > 0:
request['dataMode'] = 'raw'
request['data'] = request_ex['body']

# Add Accept header to request based on response model (hack?)
# EQD: This is not strictly correct since only 1 Accept header will appear in headers
for response in example['responses']:
for header in response['headers']:
if header['name'] != 'Content-Type':
continue
if 'Accept: ' + header['value'] not in headers:
headers.append('Accept: ' + header['value'])
request['headers'] = '\n'.join(headers)
content_types = [r['value'] for r in response['headers'] if r['name'].lower() == 'content-type']
if len(content_types) > 0 and 'Accept' not in headers:
headers['Accept'] = content_types[0]
request['headers'] = '\n'.join(['%s: %s' % (k, v) for k,v in headers.iteritems()])
# Add reference to collection to this request
# The collectionId field refers to the parent collection, not the folder
request['collectionId'] = collection['id']
Expand Down
Binary file removed apiary2postman/converter.pyc
Binary file not shown.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
download_url = 'https://github.com/thecopy/apiary2postman/tarball/0.4.8',
keywords = ['apiary', 'blueman', 'postman'], # arbitrary keywords
classifiers = [],
install_requires = ['urimagic'],
entry_points={
'console_scripts': [
'apiary2postman = apiary2postman.apiary2postman:main',
Expand Down