From 770af3883df3ab2261da0df7b149fec71aae7c47 Mon Sep 17 00:00:00 2001 From: tony Date: Sat, 23 Feb 2019 10:59:26 +0800 Subject: [PATCH 1/6] add use_args --- flask_apispec/__init__.py | 3 ++- flask_apispec/annotations.py | 36 ++++++++++++++++++++++++++++++++++++ flask_apispec/wrapper.py | 5 ++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/flask_apispec/__init__.py b/flask_apispec/__init__.py index 32cc26b..11364f8 100644 --- a/flask_apispec/__init__.py +++ b/flask_apispec/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from flask_apispec.views import ResourceMeta, MethodResource -from flask_apispec.annotations import doc, wrap_with, use_kwargs, marshal_with +from flask_apispec.annotations import doc, wrap_with, use_kwargs, marshal_with, use_args from flask_apispec.extension import FlaskApiSpec from flask_apispec.utils import Ref @@ -9,6 +9,7 @@ 'doc', 'wrap_with', 'use_kwargs', + 'use_args', 'marshal_with', 'ResourceMeta', 'MethodResource', diff --git a/flask_apispec/annotations.py b/flask_apispec/annotations.py index 6e1992c..831deaa 100644 --- a/flask_apispec/annotations.py +++ b/flask_apispec/annotations.py @@ -38,6 +38,42 @@ def wrapper(func): return wrapper +def use_args(args, locations=None, inherit=None, apply=None, **kwargs): + """Inject keyword arguments from the specified webargs arguments into the + decorated view function. + + Usage: + + .. code-block:: python + + from marshmallow import fields + + @use_args({'name': fields.Str(), 'category': fields.Str()}) + def create_pet(args): + db.session.add(args) + db.session.commit() + return args + + :param args: Mapping of argument names to :class:`Field ` + objects, :class:`Schema `, or a callable which accepts a + request and returns a :class:`Schema ` + :param locations: Default request locations to parse + :param inherit: Inherit args from parent classes + :param apply: Parse request with specified args + """ + kwargs.update({'locations': locations}) + + def wrapper(func): + options = { + 'args': args, + 'kwargs': kwargs, + 'use_args': True + } + annotate(func, 'args', [options], inherit=inherit, apply=apply) + return activate(func) + return wrapper + + def marshal_with(schema, code='default', description='', inherit=None, apply=None): """Marshal the return value of the decorated view function using the specified schema. diff --git a/flask_apispec/wrapper.py b/flask_apispec/wrapper.py index 067261f..369ef2c 100644 --- a/flask_apispec/wrapper.py +++ b/flask_apispec/wrapper.py @@ -44,7 +44,10 @@ def call_view(self, *args, **kwargs): if getattr(schema, 'many', False): args += tuple(parsed) else: - kwargs.update(parsed) + if 'use_args' in option and option['use_args']: + args += (parsed,) + else: + kwargs.update(parsed) return self.func(*args, **kwargs) def marshal_result(self, unpacked, status_code): From b427f58ddc16f5ad49ec3937c611b3c80c270439 Mon Sep 17 00:00:00 2001 From: tony Date: Sat, 23 Feb 2019 11:05:39 +0800 Subject: [PATCH 2/6] modified use_args annotation --- flask_apispec/annotations.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flask_apispec/annotations.py b/flask_apispec/annotations.py index 831deaa..7830e42 100644 --- a/flask_apispec/annotations.py +++ b/flask_apispec/annotations.py @@ -48,9 +48,9 @@ def use_args(args, locations=None, inherit=None, apply=None, **kwargs): from marshmallow import fields - @use_args({'name': fields.Str(), 'category': fields.Str()}) - def create_pet(args): - db.session.add(args) + @use_args(PetSchema(exclude=('id','created')) + def create_pet(pet): + db.session.add(pet) db.session.commit() return args From d832f90620f907564e713ad2d148cc86625e0041 Mon Sep 17 00:00:00 2001 From: tony Date: Sat, 23 Feb 2019 21:00:47 +0800 Subject: [PATCH 3/6] remove use_args, support sqlalchemy --- flask_apispec/__init__.py | 3 +-- flask_apispec/annotations.py | 37 ------------------------------------ flask_apispec/wrapper.py | 26 ++++++++++++++++++------- 3 files changed, 20 insertions(+), 46 deletions(-) diff --git a/flask_apispec/__init__.py b/flask_apispec/__init__.py index 11364f8..32cc26b 100644 --- a/flask_apispec/__init__.py +++ b/flask_apispec/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from flask_apispec.views import ResourceMeta, MethodResource -from flask_apispec.annotations import doc, wrap_with, use_kwargs, marshal_with, use_args +from flask_apispec.annotations import doc, wrap_with, use_kwargs, marshal_with from flask_apispec.extension import FlaskApiSpec from flask_apispec.utils import Ref @@ -9,7 +9,6 @@ 'doc', 'wrap_with', 'use_kwargs', - 'use_args', 'marshal_with', 'ResourceMeta', 'MethodResource', diff --git a/flask_apispec/annotations.py b/flask_apispec/annotations.py index 7830e42..4f270d5 100644 --- a/flask_apispec/annotations.py +++ b/flask_apispec/annotations.py @@ -37,43 +37,6 @@ def wrapper(func): return activate(func) return wrapper - -def use_args(args, locations=None, inherit=None, apply=None, **kwargs): - """Inject keyword arguments from the specified webargs arguments into the - decorated view function. - - Usage: - - .. code-block:: python - - from marshmallow import fields - - @use_args(PetSchema(exclude=('id','created')) - def create_pet(pet): - db.session.add(pet) - db.session.commit() - return args - - :param args: Mapping of argument names to :class:`Field ` - objects, :class:`Schema `, or a callable which accepts a - request and returns a :class:`Schema ` - :param locations: Default request locations to parse - :param inherit: Inherit args from parent classes - :param apply: Parse request with specified args - """ - kwargs.update({'locations': locations}) - - def wrapper(func): - options = { - 'args': args, - 'kwargs': kwargs, - 'use_args': True - } - annotate(func, 'args', [options], inherit=inherit, apply=apply) - return activate(func) - return wrapper - - def marshal_with(schema, code='default', description='', inherit=None, apply=None): """Marshal the return value of the decorated view function using the specified schema. diff --git a/flask_apispec/wrapper.py b/flask_apispec/wrapper.py index 369ef2c..4d38e01 100644 --- a/flask_apispec/wrapper.py +++ b/flask_apispec/wrapper.py @@ -15,12 +15,23 @@ [int(part) for part in ma.__version__.split('.') if part.isdigit()] ) +def asdict(row): + """convert Sqlalchemy instance to dict""" + result = dict() + for key in row.__mapper__.c.keys(): + if getattr(row, key) is not None: + result[key] = str(getattr(row, key)) + else: + result[key] = getattr(row, key) + return result + class Wrapper(object): """Apply annotations to a view function. :param func: View function to wrap :param instance: Optional instance or parent """ + def __init__(self, func, instance=None): self.func = func self.instance = instance @@ -44,10 +55,8 @@ def call_view(self, *args, **kwargs): if getattr(schema, 'many', False): args += tuple(parsed) else: - if 'use_args' in option and option['use_args']: - args += (parsed,) - else: - kwargs.update(parsed) + kwargs.update(asdict(parsed) + if hasattr(parsed,'__mapper__') else parsed) # support flask-marshmallow return self.func(*args, **kwargs) def marshal_result(self, unpacked, status_code): @@ -62,14 +71,17 @@ def marshal_result(self, unpacked, status_code): output = dumped.data if MARSHMALLOW_VERSION_INFO[0] < 3 else dumped else: output = unpacked[0] - return format_output((format_response(output), ) + unpacked[1:]) + return format_output((format_response(output),) + unpacked[1:]) + def identity(value): return value + def unpack(resp): - resp = resp if isinstance(resp, tuple) else (resp, ) - return resp + (None, ) * (3 - len(resp)) + resp = resp if isinstance(resp, tuple) else (resp,) + return resp + (None,) * (3 - len(resp)) + def format_output(values): while values[-1] is None: From 9c0cb68cc0ea45f847a07b7696b91e87fe7ad61c Mon Sep 17 00:00:00 2001 From: bona shen Date: Sat, 23 Feb 2019 21:29:20 +0800 Subject: [PATCH 4/6] Update wrapper.py --- flask_apispec/wrapper.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/flask_apispec/wrapper.py b/flask_apispec/wrapper.py index 4d38e01..22a59de 100644 --- a/flask_apispec/wrapper.py +++ b/flask_apispec/wrapper.py @@ -15,14 +15,14 @@ [int(part) for part in ma.__version__.split('.') if part.isdigit()] ) -def asdict(row): +def asdict(row, fill_none=True): """convert Sqlalchemy instance to dict""" result = dict() for key in row.__mapper__.c.keys(): - if getattr(row, key) is not None: - result[key] = str(getattr(row, key)) - else: + if getattr(row, key, None) is not None: result[key] = getattr(row, key) + elif not fill_none: + result[key] = None return result class Wrapper(object): From 1052f8d645966bfe771f425352a6d5b05a73a1d8 Mon Sep 17 00:00:00 2001 From: tony Date: Sun, 6 Sep 2020 20:49:52 +0800 Subject: [PATCH 5/6] add blueprint --- examples/petstore_blueprint.py | 54 ++++++++++++++++++++++++++++++++++ flask_apispec/blueprint.py | 30 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 examples/petstore_blueprint.py create mode 100644 flask_apispec/blueprint.py diff --git a/examples/petstore_blueprint.py b/examples/petstore_blueprint.py new file mode 100644 index 0000000..e3a8e33 --- /dev/null +++ b/examples/petstore_blueprint.py @@ -0,0 +1,54 @@ +import logging + +import flask.views +import marshmallow as ma + +from flask_apispec import FlaskApiSpec, MethodResource +from flask_apispec import doc, marshal_with, use_kwargs +from flask_apispec.blueprint import Blueprint + +app = flask.Flask(__name__) +docs = FlaskApiSpec(app) + +blueprint = Blueprint(docs, name='pet', import_name=__name__) + +logging.basicConfig(level=logging.DEBUG) + +class Pet: + def __init__(self, name, type): + self.name = name + self.type = type + + +class PetSchema(ma.Schema): + name = ma.fields.Str() + type = ma.fields.Str() + + +@blueprint.route('/pets/') +@doc(params={'pet_id': {'description': 'pet id'}}) +@marshal_with(PetSchema) +@use_kwargs({'breed': ma.fields.Str()}) +def get_pet(pet_id): + return Pet('calici', 'cat') + + +@blueprint.route('/cat/') +@doc( + tags=['pets'], + params={'pet_id': {'description': 'the pet name'}}, +) +class CatResource(MethodResource): + + @marshal_with(PetSchema) + def get(self, pet_id): + return Pet('calici', 'cat') + + @marshal_with(PetSchema) + def put(self, pet_id): + return Pet('calici', 'cat') + + +if __name__ == '__main__': + app.register_blueprint(blueprint) + app.run(debug=True) diff --git a/flask_apispec/blueprint.py b/flask_apispec/blueprint.py new file mode 100644 index 0000000..5740d69 --- /dev/null +++ b/flask_apispec/blueprint.py @@ -0,0 +1,30 @@ +from flask import Blueprint as FlaskBlueprint +from flask.blueprints import BlueprintSetupState +from flask_apispec import MethodResource, FlaskApiSpec +import logging +import inspect + +log = logging.getLogger(__name__) + + +class DocsBlueprintSetupState(BlueprintSetupState): + + def add_url_rule(self, rule, endpoint=None, view_func=None, **options): + log.debug('add url rule:%s' % rule) + doc_view_func = view_func + if inspect.isclass(view_func) and issubclass(view_func, MethodResource): + view_func = view_func.as_view(endpoint or view_func.__name__) + super().add_url_rule(rule, endpoint, view_func, **options) + docs = self.blueprint.docs + log.debug('register %s' % doc_view_func.__name__) + docs.register(doc_view_func, endpoint=endpoint, blueprint=self.blueprint.name) + + +class Blueprint(FlaskBlueprint): + + def __init__(self, docs: FlaskApiSpec, name: str, import_name: str, **kwargs): + super().__init__(name, import_name, **kwargs) + self.docs = docs + + def make_setup_state(self, app, options, first_registration=False): + return DocsBlueprintSetupState(self, app, options, first_registration) \ No newline at end of file From 321f0f28bc540f0154a8d4ea8df49e1b44afe62d Mon Sep 17 00:00:00 2001 From: tony Date: Sun, 6 Sep 2020 22:22:39 +0800 Subject: [PATCH 6/6] Fixed petstore_blueprint.py get_pet params. --- examples/petstore_blueprint.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/petstore_blueprint.py b/examples/petstore_blueprint.py index e3a8e33..f29c71e 100644 --- a/examples/petstore_blueprint.py +++ b/examples/petstore_blueprint.py @@ -14,6 +14,7 @@ logging.basicConfig(level=logging.DEBUG) + class Pet: def __init__(self, name, type): self.name = name @@ -28,8 +29,8 @@ class PetSchema(ma.Schema): @blueprint.route('/pets/') @doc(params={'pet_id': {'description': 'pet id'}}) @marshal_with(PetSchema) -@use_kwargs({'breed': ma.fields.Str()}) -def get_pet(pet_id): +@use_kwargs({'breed': ma.fields.Str()}, location='query') +def get_pet(pet_id,breed): return Pet('calici', 'cat')