From dc1db5d498c04c4a7dc92383747fabf68c01e6a6 Mon Sep 17 00:00:00 2001 From: Mike Reiss Date: Fri, 12 Mar 2021 13:48:05 -0500 Subject: [PATCH] Skip response fields where value is None --- flask_accepts/decorators/decorators.py | 16 ++++++++ flask_accepts/decorators/decorators_test.py | 43 +++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/flask_accepts/decorators/decorators.py b/flask_accepts/decorators/decorators.py index 4b0bf89..c6f334b 100644 --- a/flask_accepts/decorators/decorators.py +++ b/flask_accepts/decorators/decorators.py @@ -207,6 +207,7 @@ def responds( validate: bool = False, description: str = None, use_swagger: bool = True, + skip_none: bool = False, ): """ Serialize the output of a function using the Marshmallow schema to dump the results. @@ -285,6 +286,21 @@ def inner(*args, **kwargs): if envelope: serialized = OrderedDict([(envelope, serialized)]) if ordered else {envelope: serialized} + if skip_none: + def remove_none(obj): + if isinstance(obj, list): + return [remove_none(entry) for entry in obj if entry is not None] + if isinstance(obj, dict): + result = {} + for key, value in obj.items(): + value = remove_none(value) + if key is not None and value is not None: + result[key] = value + return result + return obj + + serialized = remove_none(serialized) + if not _is_method(func): # Regular route, need to manually create Response return jsonify(serialized), status_code diff --git a/flask_accepts/decorators/decorators_test.py b/flask_accepts/decorators/decorators_test.py index ce239ab..8e3da83 100644 --- a/flask_accepts/decorators/decorators_test.py +++ b/flask_accepts/decorators/decorators_test.py @@ -626,6 +626,49 @@ def get(self): assert resp.status_code == 200 assert resp.json == {'test-data': {'_id': 42, 'name': 'Jon Snow'}} + +def test_responds_skips_none_false(app, client): + class TestSchema(Schema): + _id = fields.Integer() + name = fields.String() + + api = Api(app) + + @api.route("/test") + class TestResource(Resource): + @responds(schema=TestSchema, api=api) + def get(self): + return {"_id": 42, "name": None} + + with client as cl: + resp = cl.get("/test") + assert resp.status_code == 200 + assert resp.json == {'_id': 42, 'name': None} + + +def test_responds_with_nested_skips_none_true(app, client): + class NestSchema(Schema): + _id = fields.Integer() + name = fields.String() + + class TestSchema(Schema): + name = fields.String() + child = fields.Nested(NestSchema) + + api = Api(app) + + @api.route("/test") + class TestResource(Resource): + @responds(schema=TestSchema, api=api, skip_none=True, many=True) + def get(self): + return [{"name": None, "child": {"_id": 42, "name": None}}] + + with client as cl: + resp = cl.get("/test") + assert resp.status_code == 200 + assert resp.json == [{"child": {'_id': 42}}] + + def test_accepts_with_nested_schema(app, client): # noqa class TestSchema(Schema): _id = fields.Integer()