Skip to content

Commit 5a47ed7

Browse files
committed
Merge pull request Yelp#11 from dnephin/python3
Python3 support
2 parents a4cfcd9 + 59e20e2 commit 5a47ed7

13 files changed

+72
-66
lines changed

.travis.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ env:
44
- TOX_ENV=py26
55
- TOX_ENV=py27
66
- TOX_ENV=py26-simplejson
7+
- TOX_ENV=py33
8+
- TOX_ENV=py34
79
- TOX_ENV=docs
810

911
install:
1012
- pip install tox coveralls
1113

1214
script:
13-
- make test
15+
- tox -e $TOX_ENV
1416

1517
after_success:
1618
- tox -e cover

setup.py

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
'jsonschema',
3333
'PyYAML',
3434
'setuptools',
35+
'six',
3536
],
3637
license=about['__license__']
3738
)

swagger_spec_validator/__about__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
__summary__ = "Validation of Swagger specifications"
88
__uri__ = "http://github.com/Yelp/swagger_spec_validator"
99

10-
__version__ = "1.0.9"
10+
__version__ = "1.0.10"
1111

1212
__author__ = "John Billings"
1313
__email__ = "[email protected]"

swagger_spec_validator/common.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
import sys
23

34
try:
@@ -7,6 +8,8 @@
78
from jsonschema import RefResolver
89
import jsonschema
910
from pkg_resources import resource_filename
11+
import six
12+
from six.moves.urllib import request
1013

1114
TIMEOUT_SEC = 1
1215

@@ -16,7 +19,10 @@ def wrapper(*args, **kwargs):
1619
try:
1720
method(*args, **kwargs)
1821
except Exception as e:
19-
raise SwaggerValidationError(str(e)), None, sys.exc_info()[2]
22+
six.reraise(
23+
SwaggerValidationError,
24+
SwaggerValidationError(str(e)),
25+
sys.exc_info()[2])
2026
return wrapper
2127

2228

@@ -34,6 +40,11 @@ def validate_json(json_document, schema_path):
3440
jsonschema.validate(json_document, schema, resolver=resolver)
3541

3642

43+
def load_json(url):
44+
with contextlib.closing(request.urlopen(url, timeout=TIMEOUT_SEC)) as fh:
45+
return json.loads(fh.read().decode('utf-8'))
46+
47+
3748
class SwaggerValidationError(Exception):
3849
"""Exception raised in case of a validation error."""
3950
pass

swagger_spec_validator/util.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import logging
2-
import urllib2
32

43
from swagger_spec_validator import validator12, validator20
54
from swagger_spec_validator.common import (SwaggerValidationError,
6-
TIMEOUT_SEC,
7-
json,
5+
load_json,
86
wrap_exception)
97

108

@@ -48,6 +46,6 @@ def validate_spec_url(spec_url):
4846
For Swagger 1.2, this is the URL to the resource listing in api-docs.
4947
For Swagger 2.0, this is the URL to swagger.json in api-docs.
5048
"""
51-
spec_json = json.load(urllib2.urlopen(spec_url, timeout=TIMEOUT_SEC))
49+
spec_json = load_json(spec_url)
5250
validator = get_validator(spec_json, spec_url)
5351
validator.validate_spec(spec_json, spec_url)

swagger_spec_validator/validator12.py

+17-21
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
# -*- coding: utf-8 -*-
2+
"""
3+
Validate Swagger Specs against the Swagger 1.2 Specification. The
4+
validator aims to check for full compliance with the Specification.
25
3-
# Validate Swagger Specs against the Swagger 1.2 Specification. The
4-
# validator aims to check for full compliance with the Specification.
5-
#
6-
# The validator uses the published jsonschema files for basic structural
7-
# validation, augmented with custom validation code where necessary.
8-
#
9-
# https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md
6+
The validator uses the published jsonschema files for basic structural
7+
validation, augmented with custom validation code where necessary.
8+
9+
https://github.com/wordnik/swagger-spec/blob/master/versions/1.2.md
10+
"""
1011
import logging
1112
import os
12-
import urllib2
13-
from urlparse import urlparse
13+
14+
import six
15+
from six.moves.urllib.parse import urlparse
1416

1517
from swagger_spec_validator.common import (SwaggerValidationError,
16-
TIMEOUT_SEC,
17-
json,
18+
load_json,
1819
validate_json,
1920
wrap_exception)
2021

@@ -26,10 +27,7 @@
2627

2728
def get_model_ids(api_declaration):
2829
models = api_declaration.get('models', {})
29-
model_ids = []
30-
for model in models.itervalues():
31-
model_ids.append(model['id'])
32-
return model_ids
30+
return [model['id'] for model in six.itervalues(models)]
3331

3432

3533
def get_resource_path(url, resource):
@@ -72,8 +70,7 @@ def validate_spec_url(url):
7270
"""
7371

7472
log.info('Validating %s' % url)
75-
resource_listing = json.load(urllib2.urlopen(url, timeout=TIMEOUT_SEC))
76-
validate_spec(resource_listing, url)
73+
validate_spec(load_json(url), url)
7774

7875

7976
def validate_spec(resource_listing, url):
@@ -95,8 +92,7 @@ def validate_spec(resource_listing, url):
9592
for api in resource_listing['apis']:
9693
path = get_resource_path(url, api['path'])
9794
log.info('Validating %s' % path)
98-
api_declaration = json.load(urllib2.urlopen(path, timeout=TIMEOUT_SEC))
99-
validate_api_declaration(api_declaration)
95+
validate_api_declaration(load_json(path))
10096

10197

10298
def validate_data_type(obj, model_ids, allow_arrays=True, allow_voids=False,
@@ -163,7 +159,7 @@ def validate_model(model, model_name, model_ids):
163159
'Model "%s": required property "%s" not found' %
164160
(model_name, required))
165161

166-
for prop_name, prop in model.get('properties', {}).iteritems():
162+
for prop_name, prop in six.iteritems(model.get('properties', {})):
167163
try:
168164
validate_data_type(prop, model_ids, allow_refs=True)
169165
except SwaggerValidationError as e:
@@ -219,7 +215,7 @@ def validate_api_declaration(api_declaration):
219215
for api in api_declaration['apis']:
220216
validate_api(api, model_ids)
221217

222-
for model_name, model in api_declaration.get('models', {}).iteritems():
218+
for model_name, model in six.iteritems(api_declaration.get('models', {})):
223219
validate_model(model, model_name, model_ids)
224220

225221

swagger_spec_validator/validator20.py

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import logging
22
import string
3-
import urllib2
3+
4+
import six
45

56
from swagger_spec_validator.common import (SwaggerValidationError,
6-
TIMEOUT_SEC,
7-
json,
7+
load_json,
88
validate_json,
99
wrap_exception)
1010

@@ -20,8 +20,7 @@ def validate_spec_url(spec_url):
2020
:raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
2121
"""
2222
log.info('Validating %s' % spec_url)
23-
spec_json = json.load(urllib2.urlopen(spec_url, timeout=TIMEOUT_SEC))
24-
validate_spec(spec_json)
23+
validate_spec(load_json(spec_url))
2524

2625

2726
def validate_spec(spec_json, _spec_url=None):
@@ -52,7 +51,7 @@ def validate_apis(apis):
5251
:raises: :py:class:`swagger_spec_validator.SwaggerValidationError`
5352
:raises: :py:class:`jsonschema.exceptions.ValidationError`
5453
"""
55-
for api_name, api_body in apis.iteritems():
54+
for api_name, api_body in six.iteritems(apis):
5655
api_params = api_body.get('parameters', [])
5756
validate_duplicate_param(api_params)
5857
for oper_name in api_body:

tests/validator12/run_test.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import print_function
12
import glob
23
import json
34
import os
@@ -21,7 +22,7 @@ def run_json_tests_with_func(json_test_paths, func):
2122
# e.g. "api_declarations/array_nested_fail.json"
2223
test_name = os.sep.join(json_test_path.split(os.sep)[-2:])
2324

24-
print 'Testing %s...' % test_name
25+
print('Testing %s...' % test_name)
2526

2627
if test_name.endswith('_pass.json'):
2728
func(test_data)

tests/validator12/validate_spec_test.py

+5-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import os
32

43
import mock
@@ -18,19 +17,17 @@
1817

1918

2019
def get_resource_listing():
21-
return json.load(read_contents(RESOURCE_LISTING_FILE))
20+
return read_contents(RESOURCE_LISTING_FILE)
2221

2322

2423
def test_http_success():
2524
mock_responses = make_mock_responses([API_DECLARATION_FILE])
2625

27-
with mock.patch('swagger_spec_validator.util.urllib2.urlopen',
28-
side_effect=mock_responses) as mock_urlopen:
26+
with mock.patch('swagger_spec_validator.validator12.load_json',
27+
side_effect=mock_responses) as mock_load_json:
2928
validate_spec(get_resource_listing(), 'http://localhost/api-docs')
3029

31-
mock_urlopen.assert_has_calls([
32-
mock.call('http://localhost/api-docs/foo', timeout=1),
33-
])
30+
mock_load_json.assert_called_once_with('http://localhost/api-docs/foo')
3431

3532

3633
def test_file_uri_success():
@@ -39,7 +36,7 @@ def test_file_uri_success():
3936
validate_spec(get_resource_listing(),
4037
'file://{0}'.format(RESOURCE_LISTING_FILE))
4138

42-
expected = json.load(read_contents(API_DECLARATION_FILE))
39+
expected = read_contents(API_DECLARATION_FILE)
4340
mock_api.assert_called_once_with(expected)
4441

4542

tests/validator12/validate_spec_url_test.py

+8-9
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
import mock
55
import pytest
6-
import StringIO
76

87
from swagger_spec_validator.common import SwaggerValidationError
98
from swagger_spec_validator.validator12 import validate_spec_url
@@ -14,8 +13,8 @@
1413

1514

1615
def read_contents(file_name):
17-
with open(file_name) as f:
18-
return StringIO.StringIO(f.read())
16+
with open(file_name, 'r') as f:
17+
return json.load(f)
1918

2019

2120
def make_mock_responses(file_names):
@@ -26,13 +25,13 @@ def test_http_success():
2625
mock_responses = make_mock_responses([RESOURCE_LISTING_FILE,
2726
API_DECLARATION_FILE])
2827

29-
with mock.patch('swagger_spec_validator.util.urllib2.urlopen',
30-
side_effect=mock_responses) as mock_urlopen:
28+
with mock.patch('swagger_spec_validator.validator12.load_json',
29+
side_effect=mock_responses) as mock_load_json:
3130
validate_spec_url('http://localhost/api-docs')
3231

33-
mock_urlopen.assert_has_calls([
34-
mock.call('http://localhost/api-docs', timeout=1),
35-
mock.call('http://localhost/api-docs/foo', timeout=1),
32+
mock_load_json.assert_has_calls([
33+
mock.call('http://localhost/api-docs'),
34+
mock.call('http://localhost/api-docs/foo'),
3635
])
3736

3837

@@ -41,7 +40,7 @@ def test_file_uri_success():
4140
with mock.patch(mock_string) as mock_api:
4241
validate_spec_url('file://{0}'.format(RESOURCE_LISTING_FILE))
4342

44-
expected = json.load(read_contents(API_DECLARATION_FILE))
43+
expected = read_contents(API_DECLARATION_FILE)
4544
mock_api.assert_called_once_with(expected)
4645

4746

tests/validator20/validate_rich_spec_test.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ def test_failure_on_duplicate_api_parameters(swagger_spec):
1616
swagger_spec['paths']['/pet']['parameters'] = [param1, param2]
1717
with pytest.raises(SwaggerValidationError) as exc_info:
1818
validate_spec(swagger_spec)
19-
assert("Duplicate param found with (name, in): ('foo', 'query')"
20-
in exc_info.value)
19+
assert ("Duplicate param found with (name, in): ('foo', 'query')"
20+
in str(exc_info.value))
2121

2222

2323
def test_failure_on_duplicate_operation_parameters(swagger_spec):
@@ -26,29 +26,30 @@ def test_failure_on_duplicate_operation_parameters(swagger_spec):
2626
swagger_spec['paths']['/pet']['post']['parameters'].extend([param1, param2])
2727
with pytest.raises(SwaggerValidationError) as exc_info:
2828
validate_spec(swagger_spec)
29-
assert("Duplicate param found with (name, in): ('foo', 'query')"
30-
in exc_info.value)
29+
assert ("Duplicate param found with (name, in): ('foo', 'query')"
30+
in str(exc_info.value))
3131

3232

3333
def test_failure_on_unresolvable_path_parameter(swagger_spec):
3434
swagger_spec['paths']['/pet/{foo}'] = swagger_spec['paths']['/pet']
3535
with pytest.raises(SwaggerValidationError) as exc_info:
3636
validate_spec(swagger_spec)
37-
assert("Path Parameter used is not defined: foo" in exc_info.value)
37+
assert "Path Parameter used is not defined: foo" in str(exc_info.value)
3838

3939

4040
def test_failure_on_path_parameter_used_but_not_defined(swagger_spec):
4141
swagger_spec['paths']['/user/{username}']['get']['parameters'][0]['name'] = '_'
4242
with pytest.raises(SwaggerValidationError) as exc_info:
4343
validate_spec(swagger_spec)
44-
assert("Path Parameter used is not defined: username" in exc_info.value)
44+
assert "Path Parameter used is not defined: username" in str(exc_info.value)
4545

4646

4747
def test_failure_on_unresolvable_ref_of_props_required_list(swagger_spec):
4848
swagger_spec['definitions']['Pet']['required'].append('bla')
4949
with pytest.raises(SwaggerValidationError) as exc_info:
5050
validate_spec(swagger_spec)
51-
assert "Required list has properties not defined: ['bla']" in exc_info.value
51+
assert ("Required list has properties not defined: ['bla']"
52+
in str(exc_info.value))
5253

5354
# TODO: validate definitions of references made by $ref, commented them for now.
5455
"""

tests/validator20/validate_spec_url_test.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
1+
import json
12
import mock
23
import pytest
3-
import StringIO
44

55
from swagger_spec_validator.common import SwaggerValidationError
66
from swagger_spec_validator.validator20 import validate_spec_url
77

88

99
def test_success(petstore_contents):
10-
mock_responses = [StringIO.StringIO(petstore_contents)]
1110
with mock.patch(
12-
'swagger_spec_validator.util.urllib2.urlopen',
13-
side_effect=mock_responses) as mock_urlopen:
11+
'swagger_spec_validator.validator20.load_json',
12+
return_value=json.loads(petstore_contents)) as mock_load_json:
1413
validate_spec_url('http://localhost/api-docs')
15-
mock_urlopen.assert_has_calls([
16-
mock.call('http://localhost/api-docs', timeout=1)])
14+
mock_load_json.assert_called_once_with('http://localhost/api-docs')
1715

1816

1917
def test_raise_SwaggerValidationError_on_urlopen_error():

tox.ini

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
[tox]
2-
envlist = py26,py27,py26-simplejson
2+
envlist = py26,py27,py26-simplejson,py33,py34
33

44
[testenv]
55
deps =
6+
# Pin a pre-release version of jsonschema with a fix for py3.x
7+
# Remove this once jsonschema > 2.4.0 is released
8+
git+https://github.com/Julian/jsonschema.git@168237288#egg=jsonschema
69
flake8
710
mock
811
pytest

0 commit comments

Comments
 (0)