Skip to content

Commit

Permalink
Added unit tests, modified the docs, other minor fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
Rodolfo N. Duldulao, Jr committed Dec 18, 2019
1 parent 99e668b commit 3f6745b
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 15 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ The main idea for this project is to automatically extract API specs from the us

Swirl uses the ```@restapi``` decorator to get both the routing info AND the swagger spec which is derived from the method module docs. While ```@schema``` decorator is used to mark classes to include them into the ```components/schemas``` section of the resulting OpenAPI spec.

## Recent News:
* Tornado-Swirl has now moved to my personal Github account.

## Releases:
* V. 0.1.20 -- Fix on basic auth security scheme spec, minor edits.
* V. 0.1.19 -- Modified property name regex to accomodate JSON:API query strings with square brackets.
* V. 0.1.18 -- Added support for ```example```/```examples``` for schema model types, bug fix on enum values with dash, updated swagger UI resources, code cleanup.
* V. 0.1.17 -- Added support for ```object``` (freeform object) type, added support for schema inheritance, and ```json_mime_type``` option in ```swirl.describe(...)``` to enable JSON:API authors to specify ```application/vnd.api+json```.
Expand Down
3 changes: 3 additions & 0 deletions TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,12 @@ swirl.describe(title="My REST API", description="Example API that does wonders")
#setup security scheme
scheme1 = security.APIKey('X-CUSTOM-KEY', location="header")
scheme2 = security.HTTP('bearer', bearerFormat='JWT')
scheme3 = security.HTTP('basic')

swirl.add_security_scheme('my_custom_key', scheme1)
swirl.add_security_scheme('my_http_key' , scheme2)
swirl.add_security_scheme('my_http_key2' , scheme3)



```
Expand Down
60 changes: 60 additions & 0 deletions examples/app_sec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from tornado_swirl.swagger import restapi, schema, Application, describe
from tornado_swirl import api_routes, add_security_scheme
import tornado.web
import tornado.ioloop
from tornado_swirl.openapi import security

describe(title='Test API', description='Just things to test')

#setup security scheme
scheme1 = security.HTTP('basic')

add_security_scheme('basic_scheme', scheme1)

@restapi('/path/to/api')
class MyHandler(tornado.web.RequestHandler):
async def get(self):
"""This will be the API path summary.
While the long description will be the API description.
Query Parameters:
date (date) -- Required. The target date.
sort (enum[asc, desc]) -- Optional. Sort order.
items (int) -- Optional. Number of items to display.
minimum: 100 maximum: 200
Returns:
items ([string]) -- List of random strings.
Error Responses:
400 (ErrorResponse) -- Bad Request.
500 (ErrorResponse) -- Internal Server Error.
Tags:
internal
Security:
basic_scheme
"""
self.finish()


@schema
class ErrorResponse(object):
"""Error response object.
Properties:
code (int) -- Required. Error code.
message (string) -- Error description.
"""
pass

def make_app():
return Application(api_routes(), autoreload=True)

if __name__ == "__main__":
app = make_app()
app.debug = True
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

setup(name='tornado-swirl',
python_requires='>=3.6.0',
version='0.1.19',
version='0.1.20',
url='https://github.com/rduldulao/tornado-swirl',
zip_safe=False,
packages=['tornado_swirl'],
Expand All @@ -33,5 +33,5 @@
'Programming Language :: Python :: 3.6'
],
keywords=['SWAGGER', 'OPENAPI', 'TORNADO'],
download_url='https://github.com/rduldulao/tornado-swirl/archive/v_0.1.19.tar.gz',
download_url='https://github.com/rduldulao/tornado-swirl/archive/v_0.1.20.tar.gz',
)
90 changes: 89 additions & 1 deletion tests/test_swirl.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pylint: disable=all
import tornado_swirl.swagger as swirl
import tornado_swirl.settings as settings
import tornado_swirl.openapi.security as security
from tornado_swirl import api_routes
from tornado.testing import AsyncHTTPTestCase, gen_test
from tornado.web import RequestHandler
Expand Down Expand Up @@ -606,4 +607,91 @@ def post(self):
assert obj['paths']
assert obj['paths']['/test']
assert obj['paths']['/test']['head']
assert obj['paths']['/test'].get('post') == None
assert obj['paths']['/test'].get('post') == None

@gen_test
def test_security_scheme1(self):
self.reset_settings()
swirl.describe(title='My API', description='My description')
swirl.add_security_scheme("test_basic", security.HTTP("basic"))
@swirl.restapi("/test")
class Handler(RequestHandler):

def post(self):
"""This is the simple description.
With a second line.
Long description.
With a second line.
Security:
test_basic
Request Body:
user (User) -- sample user.
"""
pass



self.get_app().add_handlers(r".*", api_routes())
response = yield self.http_client.fetch(self.get_url('/swagger/spec'))
obj = json.loads(response.body.decode('utf-8'))

assert obj['paths']
assert obj['paths']['/test']
assert obj['paths']['/test']['post']
assert obj['paths']['/test']['post']['security']
assert obj['paths']['/test']['post']['security'][0]
assert 'test_basic' in obj['paths']['/test']['post']['security'][0]


assert obj['components']
assert obj['components']['securitySchemes']
assert obj['components']['securitySchemes']['test_basic']
assert obj['components']['securitySchemes']['test_basic']['type'] == 'http'
assert obj['components']['securitySchemes']['test_basic']['scheme'] == 'basic'

@gen_test
def test_security_scheme2(self):
self.reset_settings()
swirl.describe(title='My API', description='My description')
swirl.add_security_scheme("test_basic", security.HTTP("bearer", bearerFormat="JWT"))
@swirl.restapi("/test")
class Handler(RequestHandler):

def post(self):
"""This is the simple description.
With a second line.
Long description.
With a second line.
Security:
test_basic
Request Body:
user (User) -- sample user.
"""
pass



self.get_app().add_handlers(r".*", api_routes())
response = yield self.http_client.fetch(self.get_url('/swagger/spec'))
obj = json.loads(response.body.decode('utf-8'))

assert obj['paths']
assert obj['paths']['/test']
assert obj['paths']['/test']['post']
assert obj['paths']['/test']['post']['security']
assert obj['paths']['/test']['post']['security'][0]
assert 'test_basic' in obj['paths']['/test']['post']['security'][0]


assert obj['components']
assert obj['components']['securitySchemes']
assert obj['components']['securitySchemes']['test_basic']
assert obj['components']['securitySchemes']['test_basic']['type'] == 'http'
assert obj['components']['securitySchemes']['test_basic']['scheme'] == 'bearer'
assert obj['components']['securitySchemes']['test_basic']['bearerFormat'] == 'JWT'
4 changes: 2 additions & 2 deletions tornado_swirl/docparser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# pylint: disable=W0611
# pylint: disable=W0611, line-too-long, unused-argument
"""Docstring line parser implementation.
Returns:
Expand Down Expand Up @@ -77,7 +77,7 @@ def _process_security_params(fsm_obj, ptype):
cleaned_lines = _clean_lines(lines)
params = {}
# parse the lines
for i, line in enumerate(cleaned_lines):
for _, line in enumerate(cleaned_lines):
matcher = PARAM_MATCHER.match(line.lstrip())
if not matcher:
continue
Expand Down
11 changes: 4 additions & 7 deletions tornado_swirl/openapi/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

class SecurityScheme(object):
"""Represents security scheme"""
pass

class APIKey(SecurityScheme):
Expand All @@ -24,16 +25,16 @@ def spec(self):
class HTTP(SecurityScheme):
Schemes = ['basic', 'bearer', 'digest', 'hoba', 'mutual', 'negotiate', 'oauth', 'scram-sha-1',
'scram-sha-256', 'vapid'] #from http://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml


def __init__(self, scheme, bearerFormat=None):
self.scheme = scheme
self.bearerFormat = bearerFormat

@property
def type(self):
return "http"

def spec(self):
sp = {
"type": "http",
Expand All @@ -42,7 +43,3 @@ def spec(self):
if self.bearerFormat:
sp["bearerFormat"] = self.bearerFormat
return sp




6 changes: 3 additions & 3 deletions tornado_swirl/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def get(self):
proto = proto or self.request.headers.get(
"X-Forwarded-Proto", None) or self.request.protocol
servers = [{
'url': proto + "://" + server_host + "/",
'url': proto + "://" + server_host,
'description': 'Default server'
}]

Expand Down Expand Up @@ -124,8 +124,8 @@ def get(self):

for name, scheme in security_schemes.items():
components['securitySchemes'][name] = scheme.spec()
specs.update(components)

#specs.update(components)
self.finish(json_dumps(specs, self.get_arguments('pretty')))

def __get_schema_spec(self, cls):
Expand Down

0 comments on commit 3f6745b

Please sign in to comment.