Skip to content

Commit

Permalink
Move swagger schema model creation and validation to a single callsit…
Browse files Browse the repository at this point in the history
…e to support #73 and in preparation for #81
  • Loading branch information
Daniel Nephin committed Feb 20, 2015
1 parent c259561 commit 4c624c3
Show file tree
Hide file tree
Showing 10 changed files with 72 additions and 74 deletions.
4 changes: 4 additions & 0 deletions pyramid_swagger/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
"""
import pyramid
from .api import register_api_doc_endpoints
from .ingest import add_swagger_schema


def includeme(config):
add_swagger_schema(config.registry)

config.add_tween(
"pyramid_swagger.tween.validation_tween_factory",
under=pyramid.tweens.EXCVIEW
)

if config.registry.settings.get(
'pyramid_swagger.enable_api_doc_views',
True
Expand Down
15 changes: 3 additions & 12 deletions pyramid_swagger/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,12 @@
"""
import simplejson

from .ingest import compile_swagger_schema
from .tween import load_settings


def register_api_doc_endpoints(config):
"""Create and register pyramid endpoints for /api-docs*.
"""
(
schema_dir,
enable_swagger_spec_validation, _, _,
) = load_settings(config.registry)
swagger_schema = compile_swagger_schema(
schema_dir,
enable_swagger_spec_validation,
)
swagger_schema = config.registry.settings['swagger_schema']
with open(swagger_schema.resource_listing) as input_file:
register_resource_listing(config, simplejson.load(input_file))

Expand Down Expand Up @@ -77,7 +67,8 @@ def register_api_declaration(config, resource_name, api_declaration):

def build_api_declaration_view(api_declaration_json):
"""Thanks to the magic of closures, this means we gracefully return JSON
without file IO at request time."""
without file IO at request time.
"""
def view_for_api_declaration(request):
# Note that we rewrite basePath to always point at this server's root.
return dict(
Expand Down
32 changes: 27 additions & 5 deletions pyramid_swagger/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,46 @@ def _load_resource_listing(resource_listing):
)


def compile_swagger_schema(schema_dir, should_validate_schemas):
def compile_swagger_schema(schema_dir):
"""Build a SwaggerSchema from various files.
:param schema_dir: the directory schema files live inside
:type schema_dir: string
:param should_validate_schemas: if True, check schemas for correctness
:type should_validate_schemas: boolean
:returns: a SwaggerSchema object
"""
listing_filename = os.path.join(schema_dir, API_DOCS_FILENAME)
listing_json = _load_resource_listing(listing_filename)
mapping = build_schema_mapping(schema_dir, listing_json)
schema_resolvers = ingest_resources(mapping, schema_dir)
return SwaggerSchema(listing_filename, mapping, schema_resolvers)


if should_validate_schemas:
# TODO: more test cases
def add_swagger_schema(registry):
"""Add the swagger_schema to the registry.settings
"""
schema_dir = registry.settings.get(
'pyramid_swagger.schema_directory',
'api_docs/'
)
if registry.settings.get(
'pyramid_swagger.enable_swagger_spec_validation',
True
):
listing_filename = os.path.join(schema_dir, API_DOCS_FILENAME)
# TODO: this will be replaced by ssv shortly
listing_json = _load_resource_listing(listing_filename)
mapping = build_schema_mapping(schema_dir, listing_json)
validate_swagger_schemas(listing_filename, mapping.values())

return SwaggerSchema(listing_filename, mapping, schema_resolvers)
# TODO: docs for this
if registry.settings.get(
'pyramid_swagger.enable_build_swagger_schema_model',
True
):
registry.settings['swagger_schema'] = (
compile_swagger_schema(schema_dir)
)


def ingest_resources(mapping, schema_dir):
Expand Down
3 changes: 0 additions & 3 deletions pyramid_swagger/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ def validate_resource_listing(resource_listing_json):

def validate_api_declaration(api_declaration_json):
"""Validate a swagger schema.
:param schema_dir: the directory schema files live inside
:type schema_dir: string
"""
api_spec_path = resource_filename(
'pyramid_swagger',
Expand Down
23 changes: 2 additions & 21 deletions pyramid_swagger/tween.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import simplejson
from jsonschema.validators import Draft3Validator, Draft4Validator
from pyramid.httpexceptions import HTTPClientError, HTTPInternalServerError
from .ingest import compile_swagger_schema
from .model import PathNotMatchedError


Expand All @@ -30,14 +29,8 @@ def validation_tween_factory(handler, registry):
Note this is very simple -- it validates requests and responses while
delegating to the relevant matching view.
"""
(
schema_dir,
enable_swagger_spec_validation,
enable_response_validation,
skip_validation
) = load_settings(registry)

schema = compile_swagger_schema(schema_dir, enable_swagger_spec_validation)
enable_response_validation, skip_validation = load_settings(registry)
schema = registry.settings['swagger_schema']
route_mapper = registry.queryUtility(IRoutesMapper)

def validator_tween(request):
Expand Down Expand Up @@ -66,16 +59,6 @@ def validator_tween(request):


def load_settings(registry):
schema_dir = registry.settings.get(
'pyramid_swagger.schema_directory',
'api_docs/'
)

enable_swagger_spec_validation = registry.settings.get(
'pyramid_swagger.enable_swagger_spec_validation',
True
)

enable_response_validation = registry.settings.get(
'pyramid_swagger.enable_response_validation',
True
Expand All @@ -93,8 +76,6 @@ def load_settings(registry):
skip_validation_res = [re.compile(skip_validation)]

return (
schema_dir,
enable_swagger_spec_validation,
enable_response_validation,
skip_validation_res,
)
Expand Down
40 changes: 18 additions & 22 deletions tests/acceptance/internal_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import pytest
import jsonschema.exceptions
import mock
import pyramid_swagger
import pyramid_swagger.tween
import pytest
import simplejson
import mock
from pyramid.httpexceptions import HTTPInternalServerError

from pyramid.config import Configurator
from pyramid_swagger.tween import validation_tween_factory
from pyramid.httpexceptions import HTTPInternalServerError
from pyramid.registry import Registry
from pyramid.response import Response
from pyramid_swagger.ingest import compile_swagger_schema
from pyramid_swagger.tween import validation_tween_factory
import pyramid.testing


Expand All @@ -31,7 +32,8 @@ def _validate_against_tween(request, response=None, settings=None):
def handler(request):
return response or Response()
settings = settings or dict({
'pyramid_swagger.schema_directory': 'tests/sample_schemas/good_app/',
'swagger_schema': compile_swagger_schema(
'tests/sample_schemas/good_app/'),
'pyramid_swagger.enable_swagger_spec_validation': False,
})
registry = get_registry(settings=settings)
Expand All @@ -54,7 +56,8 @@ def test_response_validation_enabled_by_default():
headers={'Content-Type': 'application/json; charset=UTF-8'},
)
settings = {
'pyramid_swagger.schema_directory': 'tests/sample_schemas/good_app/',
'swagger_schema': compile_swagger_schema(
'tests/sample_schemas/good_app/'),
'pyramid_swagger.enable_swagger_spec_validation': False,
}
with pytest.raises(HTTPInternalServerError):
Expand All @@ -69,7 +72,8 @@ def test_500_when_response_is_missing_required_field():
matchdict={'path_arg': 'path_arg1'},
)
settings = {
'pyramid_swagger.schema_directory': 'tests/sample_schemas/good_app/',
'swagger_schema': compile_swagger_schema(
'tests/sample_schemas/good_app/'),
'pyramid_swagger.enable_swagger_spec_validation': False,
}
# Omit the logging_info key from the response.
Expand All @@ -89,7 +93,8 @@ def test_200_when_response_is_void():
matchdict={'int_arg': '1', 'float_arg': '2.0', 'boolean_arg': 'true'},
)
settings = {
'pyramid_swagger.schema_directory': 'tests/sample_schemas/good_app/',
'swagger_schema': compile_swagger_schema(
'tests/sample_schemas/good_app/'),
'pyramid_swagger.enable_swagger_spec_validation': False,
}
response = Response(
Expand All @@ -111,7 +116,8 @@ def test_500_when_response_arg_is_wrong_type():
'logging_info': {'foo': 'bar'}
}
settings = {
'pyramid_swagger.schema_directory': 'tests/sample_schemas/good_app/',
'swagger_schema': compile_swagger_schema(
'tests/sample_schemas/good_app/'),
'pyramid_swagger.enable_swagger_spec_validation': False,
}
response = Response(
Expand All @@ -122,20 +128,10 @@ def test_500_when_response_arg_is_wrong_type():
_validate_against_tween(request, response=response, settings=settings)


def test_bad_schema_validated_on_tween_creation_by_default():
settings = {
'pyramid_swagger.schema_directory':
'tests/sample_schemas/bad_app/',
}
registry = get_registry(settings=settings)
with pytest.raises(jsonschema.exceptions.ValidationError):
validation_tween_factory(mock.ANY, registry)


def test_bad_schema_not_validated_if_spec_validation_is_disabled():
settings = {
'pyramid_swagger.schema_directory':
'tests/sample_schemas/bad_app/',
'swagger_schema': compile_swagger_schema(
'tests/sample_schemas/bad_app/'),
'pyramid_swagger.enable_swagger_spec_validation': False,
}
registry = get_registry(settings=settings)
Expand Down
5 changes: 4 additions & 1 deletion tests/api_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pyramid_swagger.ingest import API_DOCS_FILENAME
from pyramid_swagger.ingest import ApiDeclarationNotFoundError
from pyramid_swagger.ingest import ResourceListingNotFoundError
from pyramid_swagger.ingest import compile_swagger_schema
from tests.acceptance.internal_test import get_registry


Expand All @@ -21,7 +22,9 @@ def test_basepath_rewriting():

def build_config(schema_dir):
return mock.Mock(
registry=get_registry({'pyramid_swagger.schema_directory': schema_dir})
registry=get_registry({
'swagger_schema': compile_swagger_schema(schema_dir),
})
)


Expand Down
13 changes: 13 additions & 0 deletions tests/includeme_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import jsonschema
import mock
import pytest

import pyramid_swagger

Expand All @@ -7,7 +9,18 @@
def test_disable_api_doc_views(mock_register):
settings = {
'pyramid_swagger.enable_api_doc_views': False,
'pyramid_swagger.enable_swagger_spec_validation': False,
'pyramid_swagger.enable_build_swagger_schema_model': False,
}
mock_config = mock.Mock(registry=mock.Mock(settings=settings))
pyramid_swagger.includeme(mock_config)
assert not mock_register.called


def test_bad_schema_validated_on_include():
settings = {
'pyramid_swagger.schema_directory': 'tests/sample_schemas/bad_app/',
}
mock_config = mock.Mock(registry=mock.Mock(settings=settings))
with pytest.raises(jsonschema.exceptions.ValidationError):
pyramid_swagger.includeme(mock_config)
5 changes: 1 addition & 4 deletions tests/model_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@

@pytest.fixture
def schema():
schema_dir = 'tests/sample_schemas/good_app/'
enable_swagger_spec_validation = False

return compile_swagger_schema(schema_dir, enable_swagger_spec_validation)
return compile_swagger_schema('tests/sample_schemas/good_app/')


def test_swagger_schema_for_request_different_methods(schema):
Expand Down
6 changes: 0 additions & 6 deletions tests/tweens_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@


from pyramid_swagger import tween
from pyramid_swagger.tween import load_settings
from pyramid_swagger.tween import prepare_body
from pyramid_swagger.tween import should_skip_validation
from pyramid_swagger.tween import validate_outgoing_response
Expand All @@ -22,11 +21,6 @@ def test_response_charset_missing_raises_5xx():
)


def test_unconfigured_schema_dir_uses_api_docs():
"""If we send a settings dict without schema_dir, fail fast."""
assert load_settings(mock.Mock(settings={}))[0] == 'api_docs/'


def test_validation_skips_path_properly():
skip_res = [re.compile(r) for r in tween.SKIP_VALIDATION_DEFAULT]
assert should_skip_validation(skip_res, '/static')
Expand Down

0 comments on commit 4c624c3

Please sign in to comment.