Skip to content

Commit

Permalink
Add a single-file example
Browse files Browse the repository at this point in the history
  • Loading branch information
zupo committed Apr 14, 2019
1 parent 533ab47 commit f219bf1
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 31 deletions.
17 changes: 11 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ jobs:
# Download and cache dependencies
- restore_cache:
keys:
- v3-dependencies-{{ checksum "Pipfile.lock" }}
- v4-dependencies-{{ checksum "Pipfile.lock" }}
# fallback to using the latest cache if no exact match is found
- v3-dependencies-
- v4-dependencies-

- run:
name: install dependencies
Expand All @@ -54,13 +54,18 @@ jobs:
- save_cache:
paths:
- ./.venv
key: v3-dependencies-{{ checksum "Pipfile.lock" }}
key: v4-dependencies-{{ checksum "Pipfile.lock" }}

- run:
name: run tests
command: |
make tests
- run:
name: run tests for the singlefile example
command: |
cd examples/singlefile && pipenv run python -m unittest app.py
release:
<<: *defaults

Expand All @@ -69,9 +74,9 @@ jobs:

- restore_cache:
keys:
- v3-dependencies-{{ checksum "Pipfile.lock" }}
- v4-dependencies-{{ checksum "Pipfile.lock" }}
# fallback to using the latest cache if no exact match is found
- v3-dependencies-
- v4-dependencies-

- run:
name: install dependencies
Expand All @@ -84,7 +89,7 @@ jobs:
- save_cache:
paths:
- ./.venv
key: v3-dependencies-{{ checksum "Pipfile.lock" }}
key: v4-dependencies-{{ checksum "Pipfile.lock" }}

- run:
name: verify git tag vs. version
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pytest = "*"
pytest-cov = "*"
pytest-randomly = "*"
twine = "*"
webtest = "*"

[packages]
"pyramid-openapi3" = {editable = true, path = "."}
71 changes: 47 additions & 24 deletions Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

150 changes: 150 additions & 0 deletions examples/singlefile/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
"""A single-file demo of pyramid_openapi3.
Usage:
* git clone https://github.com/niteoweb/pyramid_openapi3.git
* cd pyramid_openapi3/examples/singlefile
* virtualenv -p python3.7 .
* source bin/activate
* pip install pyramid_openapi3
* python app.py
"""

from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPForbidden
from pyramid.view import view_config
from wsgiref.simple_server import make_server

import tempfile
import unittest

# This is usually in a separate openapi.yaml file, but for the sake of the
# example we want everything in a single file. Other examples have it nicely
# separated.
OPENAPI_DOCUMENT = (
b'openapi: "3.0.0"\n'
b"info:\n"
b' version: "1.0.0"\n'
b" title: Ping API\n"
b"paths:\n"
b" /hello:\n"
b" get:\n"
b" parameters:\n"
b" - name: name\n"
b" in: query\n"
b" required: true\n"
b" schema:\n"
b" type: string\n"
b" minLength: 3\n"
b" responses:\n"
b" 200:\n"
b" description: Say hi!\n"
)


@view_config(route_name="hello", renderer="json", request_method="GET", openapi=True)
def hello(request):
"""Say hello."""
if request.openapi_validated.parameters["query"]["name"] == "admin":
raise HTTPForbidden()
return {"hello": request.openapi_validated.parameters["query"]["name"]}


def app(spec):
"""Prepare a Pyramid app."""
with Configurator() as config:
config.include("pyramid_openapi3")
config.pyramid_openapi3_spec(spec)
config.pyramid_openapi3_add_explorer()
config.add_route("hello", "/hello")
config.scan(".")
return config.make_wsgi_app()


if __name__ == "__main__":
"""If app.py is called directly, start up the app."""
with tempfile.NamedTemporaryFile() as document:
document.write(OPENAPI_DOCUMENT)
document.seek(0)

print("visit api explorer at http://0.0.0.0:6543/docs/") # noqa: T001
server = make_server("0.0.0.0", 6543, app(document.name))
server.serve_forever()


#######################################
# ---- Tests ---- #
# A couple of functional tests to #
# showcase pyramid_openapi3 features. #
# Usage: python -m unittest app.py #
#######################################

from openapi_core.schema.parameters.exceptions import InvalidParameterValue # noqa
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter # noqa
from openapi_core.schema.responses.exceptions import InvalidResponse # noqa


class FunctionalTests(unittest.TestCase):
"""A suite of tests that make actual requests to a running app."""

def setUp(self):
"""Start up the app so that tests can send requests to it."""
from webtest import TestApp

with tempfile.NamedTemporaryFile() as document:
document.write(OPENAPI_DOCUMENT)
document.seek(0)

self.testapp = TestApp(app(document.name))

def test_nothing_on_root(self):
"""We have not configured our app to serve anything on root."""
res = self.testapp.get("/", status=404)
self.assertIn("404 Not Found", res.text)

def test_api_explorer_on_docs(self):
"""Swagger's API Explorer should be served on /docs/."""
res = self.testapp.get("/docs/", status=200)
self.assertIn("<title>Swagger UI</title>", res.text)

def test_hello(self):
"""Say hello."""
res = self.testapp.get("/hello?name=john", status=200)
self.assertIn('{"hello": "john"}', res.text)

def test_undefined_response(self):
"""Saying hello to admin should fail with 403 Forbidden.
But because we have not defined how a 403 response should look in
OPENAPI_DOCUMENT, we instead get a InvalidResponse exception.
This is to prevent us from forgetting to define all possible responses
in our opeanapi document.
"""
try:
self.testapp.get("/hello?name=admin", status=200)
except InvalidResponse as exc:
self.assertEqual(str(exc), "Unknown response http status: 403")

def test_name_missing(self):
"""Our view code does not even get called is request is not per-spec.
We don't have to write (and test!) any validation code in our view!
"""
try:
self.testapp.get("/hello", status=200)
except MissingRequiredParameter as exc:
self.assertEqual(str(exc), "Missing required parameter: name")

def test_too_short_name(self):
"""A too short name is picked up by openapi-core validation.
We don't have to write (and test!) validation any code in our view!
"""
try:
self.testapp.get("/hello?name=yo", status=200)
except InvalidParameterValue as exc:
self.assertEqual(
str(exc),
"Invalid parameter value for `name`: Value is shorter (2) "
"than the minimum length of 3",
)
2 changes: 1 addition & 1 deletion pyramid_openapi3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def wrapper_view(context, request):
)
else:
# Do the view
request.openapi_validated.raise_for_errors()
response = view(context, request)

except HTTPException as exc:
Expand Down Expand Up @@ -151,7 +152,6 @@ def spec_view(request):

config.add_route(route_name, route)
config.add_view(route_name=route_name, view=spec_view)
config.add_view(route_name=route_name, view=spec_view)

custom_formatters = config.registry.settings.get("pyramid_openapi3_formatters")

Expand Down

0 comments on commit f219bf1

Please sign in to comment.