Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blueprint and arg fixes #70

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
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
40 changes: 39 additions & 1 deletion flask_apispec/apidoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from marshmallow.utils import is_instance_or_subclass

from flask_apispec.paths import rule_to_path, rule_to_params
from flask_apispec.utils import resolve_resource, resolve_annotations, merge_recursive
from flask_apispec.utils import resolve_instance, resolve_annotations, merge_recursive
import inspect


class Converter(object):

Expand Down Expand Up @@ -59,9 +61,15 @@ def get_operation(self, rule, view, parent=None):
'responses': self.get_responses(view, parent),
'parameters': self.get_parameters(rule, view, docs, parent),
}
description = self.get_description(view)
if description:
operation['description'] = description
docs.pop('params', None)
return merge_recursive([operation, docs])

def get_description(self, view):
return None

def get_parent(self, view):
return None

Expand Down Expand Up @@ -100,6 +108,9 @@ class ViewConverter(Converter):
def get_operations(self, rule, view):
return {method: view for method in rule.methods}

def get_parent(self, resource, **kwargs):
return resource.method_view if hasattr(resource, 'method_view') else None

class ResourceConverter(Converter):

def get_operations(self, rule, resource):
Expand All @@ -111,3 +122,30 @@ def get_operations(self, rule, resource):

def get_parent(self, resource, **kwargs):
return resolve_resource(resource, **kwargs)


class ClassfulConverter(Converter):

def convert(self, classful_meta):
paths = list()
route = classful_meta['route']
rule = classful_meta['rule']
rules = self.app.url_map._rules_by_endpoint[route]
for rule in rules:
paths.append(self.get_path(rule, classful_meta, classful_meta=classful_meta))

return paths

def get_description(self, view):
return inspect.getdoc(view)

def get_operations(self, rule, endpoint):
# remove OPTIONS (its for CORS)
methods = set(rule.methods) - {'OPTIONS'}
return {
method: endpoint['view_func']
for method in methods
}

def get_parent(self, resource, classful_meta, **kwargs):
return classful_meta['target']
27 changes: 22 additions & 5 deletions flask_apispec/extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from apispec.ext.marshmallow import MarshmallowPlugin

from flask_apispec import ResourceMeta
from flask_apispec.apidoc import ViewConverter, ResourceConverter
from flask_apispec.apidoc import ViewConverter, ResourceConverter, ClassfulConverter


class FlaskApiSpec(object):
Expand Down Expand Up @@ -49,6 +49,9 @@ def __init__(self, app=None):

def init_app(self, app):
self.app = app
self.view_converter = ViewConverter(self.app)
self.resource_converter = ResourceConverter(self.app)
self.classful_converter = ClassfulConverter(self.app)
self.spec = self.app.config.get('APISPEC_SPEC') or \
make_apispec(self.app.config.get('APISPEC_TITLE', 'flask-apispec'),
self.app.config.get('APISPEC_VERSION', 'v1'))
Expand Down Expand Up @@ -93,12 +96,23 @@ def swagger_ui(self):
def register_existing_resources(self):
for name, rule in self.app.view_functions.items():
try:
blueprint_name, _ = name.split('.')
blueprint_name, endpoint_name = name.split('.')
except ValueError:
endpoint_name = name
blueprint_name = None

# don't auto-register ResourceMeta endpoints
if hasattr(rule, 'view_class'):
view_class = rule.view_class
if isinstance(view_class, ResourceMeta):
continue
try:
self.register(rule, blueprint=blueprint_name)
if hasattr(rule, '_classful_meta'):
self._register(
rule,
blueprint=blueprint_name,
)
else:
self.register(rule, endpoint=endpoint_name, blueprint=blueprint_name)
except TypeError:
pass

Expand Down Expand Up @@ -130,7 +144,10 @@ def _register(self, target, endpoint=None, blueprint=None,
:param dict resource_class_kwargs: (optional) kwargs to be forwarded to
the view class constructor.
"""
if isinstance(target, types.FunctionType):
if hasattr(target, '_classful_meta'):
classful_meta = getattr(target, '_classful_meta')
paths = self.classful_converter.convert(classful_meta)
elif isinstance(target, types.FunctionType):
paths = self.view_converter.convert(target, endpoint, blueprint)
elif isinstance(target, ResourceMeta):
paths = self.resource_converter.convert(
Expand Down
1 change: 1 addition & 0 deletions flask_apispec/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def rule_to_params(rule, overrides=None):
result = [
argument_to_param(argument, rule, overrides.get(argument, {}))
for argument in rule.arguments
if argument in rule._converters
]
for key in overrides.keys():
if overrides[key].get('in') in ('header', 'query'):
Expand Down
1 change: 1 addition & 0 deletions flask_apispec/wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def marshal_result(self, unpacked, status_code):
dumped = schema.dump(unpacked[0])
output = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped
else:
format_response = identity
output = unpacked[0]
return format_output((format_response(output), ) + unpacked[1:])

Expand Down
20 changes: 20 additions & 0 deletions tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,26 @@ def get(self, **kwargs):
docs.register(BandResource, endpoint='band')
assert '/bands/{band_id}/' in docs.spec._paths

def test_register_classful(self, app, docs):
@doc(tags=['band'])
class BandResource(MethodResource):
def get(self, **kwargs):
return 'slowdive'
route = '/bands/<band_id>/'
view_func = BandResource.as_view('band')
app.add_url_rule(route, view_func=view_func)
rule = docs.app.view_functions['band']
meta = dict(
view_func=view_func,
rule=rule,
route='band',
target=BandResource,
methods=['GET'],
)
setattr(view_func, "_classful_meta", meta)
docs.register_existing_resources()
assert '/bands/{band_id}/' in docs.spec._paths

def test_register_resource_with_constructor_args(self, app, docs):
@doc(tags=['band'])
class BandResource(MethodResource):
Expand Down