Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
Changelog
=========

8.0.2 (2016-XX-XX)
8.1.0 (2016-XX-XX)
------------------
- Support for YAML Swagger specs
- Remove pytest-mock dependency from requirements-dev.txt. No longer used and it was breaking the build.
- Requires bravado-core >= 4.1.0

8.0.1 (2015-12-02)
------------------
Expand Down
2 changes: 1 addition & 1 deletion bravado/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
version = '8.0.1'
version = '8.1.0'
48 changes: 41 additions & 7 deletions bravado/swagger_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
import contextlib
import logging
import os
import os.path
import yaml

from bravado_core.spec import is_yaml
from six.moves import urllib
from six.moves.urllib import parse as urlparse

Expand All @@ -24,22 +27,25 @@ class FileEventual(object):
class FileResponse(object):

def __init__(self, data):
self.data = data
self.text = data
self.headers = {}

def json(self):
return self.data
return json.loads(self.text)

def __init__(self, path):
self.path = path
self.is_yaml = is_yaml(path)

def get_path(self):
if not self.path.endswith('.json'):
if not self.path.endswith('.json') and not self.is_yaml:
return self.path + '.json'
return self.path

def wait(self, timeout=None):
with contextlib.closing(urllib.request.urlopen(self.get_path())) as fp:
return self.FileResponse(json.load(fp))
content = fp.read()
return self.FileResponse(content)

def result(self, *args, **kwargs):
return self.wait(*args, **kwargs)
Expand Down Expand Up @@ -86,12 +92,40 @@ def load_spec(self, spec_url, base_url=None):
:param base_url: TODO: need this?
:returns: json spec in dict form
"""
spec_json = request(
response = request(
self.http_client,
spec_url,
self.request_headers,
).result().json()
return spec_json
).result()

content_type = response.headers.get('content-type', '').lower()
if is_yaml(spec_url, content_type):
return self.load_yaml(response.text)
else:
return response.json()

def load_yaml(self, text):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just load them all as if they're yaml? Json is just a yaml subset

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but YAML is not a JSON subset, we'd be potentially parsing/ingesting files that are not valid JSON.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm I guess that's fair.

"""Load a YAML Swagger spec from the given string, transforming
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggests to me that your YAML spec is incorrect, we shouldn't need to cast these to string. Need to wrap in quotes.
https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responses-object-example

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, gotcha, sounds like specs out there aren't well formed in general and yaml might be inconsistent. Cool

integer response status codes to strings. This is to keep
compatibility with the existing YAML spec examples in
https://github.com/OAI/OpenAPI-Specification/tree/master/examples/v2.0/yaml
:param text: String from which to parse the YAML.
:type text: basestring
:return: Python dictionary representing the spec.
:raise: yaml.parser.ParserError: If the text is not valid YAML.
"""
data = yaml.load(text)
for path, methods in iter(data.get('paths', {}).items()):
for method, operation in iter(methods.items()):
if 'responses' in operation:
operation['responses'] = dict(
(str(code), response)
for code, response in iter(
operation['responses'].items()
)
)

return data


# TODO: Adding the file scheme here just adds complexity to request()
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@
"Programming Language :: Python :: 3.4",
],
install_requires=[
"bravado-core >= 4.0.0",
"bravado-core >= 4.1.0",
"crochet >= 1.4.0",
"fido >= 2.1.0",
"python-dateutil",
"pyyaml",
"requests",
"six",
"twisted >= 14.0.0, < 15.5.0", # Python 2.6 support was dropped in 15.5.0
Expand Down
136 changes: 38 additions & 98 deletions test-data/2.0/petstore/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,7 @@
"405": {
"description": "Invalid input"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
},
"put": {
"tags": [
Expand Down Expand Up @@ -118,15 +110,7 @@
"405": {
"description": "Validation exception"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
}
},
"/pet/findByStatus": {
Expand All @@ -135,7 +119,7 @@
"pet"
],
"summary": "Finds Pets by status",
"description": "Multiple status values can be provided with comma seperated strings",
"description": "Multiple status values can be provided with comma separated strings",
"operationId": "findPetsByStatus",
"produces": [
"application/xml",
Expand All @@ -157,7 +141,7 @@
],
"default": "available"
},
"collectionFormat": "csv"
"collectionFormat": "multi"
}
],
"responses": {
Expand All @@ -173,15 +157,7 @@
"400": {
"description": "Invalid status value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
}
},
"/pet/findByTags": {
Expand All @@ -190,7 +166,7 @@
"pet"
],
"summary": "Finds Pets by tags",
"description": "Muliple tags can be provided with comma seperated strings. Use tag1, tag2, tag3 for testing.",
"description": "Muliple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.",
"operationId": "findPetsByTags",
"produces": [
"application/xml",
Expand All @@ -206,7 +182,7 @@
"items": {
"type": "string"
},
"collectionFormat": "csv"
"collectionFormat": "multi"
}
],
"responses": {
Expand All @@ -223,14 +199,7 @@
"description": "Invalid tag value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
"deprecated": true
}
},
"/pet/{petId}": {
Expand Down Expand Up @@ -317,15 +286,7 @@
"405": {
"description": "Invalid input"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
},
"delete": {
"tags": [
Expand Down Expand Up @@ -356,17 +317,12 @@
],
"responses": {
"400": {
"description": "Invalid pet value"
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
"description": "Invalid ID supplied"
},
"404": {
"description": "Pet not found"
}
]
}
}
},
"/pet/{petId}/uploadImage": {
Expand Down Expand Up @@ -414,15 +370,7 @@
"$ref": "#/definitions/ApiResponse"
}
}
},
"security": [
{
"petstore_auth": [
"write:pets",
"read:pets"
]
}
]
}
}
},
"/store/inventory": {
Expand Down Expand Up @@ -498,7 +446,7 @@
"store"
],
"summary": "Find purchase order by ID",
"description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions",
"description": "For valid response try integer IDs with value >= 1 and <= 10. Other values will generated exceptions",
"operationId": "getOrderById",
"produces": [
"application/xml",
Expand All @@ -511,7 +459,7 @@
"description": "ID of pet that needs to be fetched",
"required": true,
"type": "integer",
"maximum": 5,
"maximum": 10,
"minimum": 1,
"format": "int64"
}
Expand All @@ -536,7 +484,7 @@
"store"
],
"summary": "Delete purchase order by ID",
"description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors",
"description": "For valid response try integer IDs with positive integer value. Negative or non-integer values will generate API errors",
"operationId": "deleteOrder",
"produces": [
"application/xml",
Expand All @@ -548,8 +496,9 @@
"in": "path",
"description": "ID of the order that needs to be deleted",
"required": true,
"type": "string",
"minimum": 1
"type": "integer",
"minimum": 1,
"format": "int64"
}
],
"responses": {
Expand Down Expand Up @@ -701,7 +650,7 @@
"X-Expires-After": {
"type": "string",
"format": "date-time",
"description": "date in UTC when toekn expires"
"description": "date in UTC when token expires"
}
}
},
Expand Down Expand Up @@ -782,7 +731,7 @@
{
"name": "username",
"in": "path",
"description": "name that need to be deleted",
"description": "name that need to be updated",
"required": true,
"type": "string"
},
Expand Down Expand Up @@ -837,15 +786,6 @@
}
},
"securityDefinitions": {
"petstore_auth": {
"type": "oauth2",
"authorizationUrl": "http://petstore.swagger.io/api/oauth/dialog",
"flow": "implicit",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"api_key": {
"type": "apiKey",
"name": "api_key",
Expand Down Expand Up @@ -955,6 +895,21 @@
"name": "Tag"
}
},
"ApiResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
},
"Pet": {
"type": "object",
"required": [
Expand Down Expand Up @@ -1006,21 +961,6 @@
"xml": {
"name": "Pet"
}
},
"ApiResponse": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"type": {
"type": "string"
},
"message": {
"type": "string"
}
}
}
},
"externalDocs": {
Expand Down
Loading