Open kam193 opened 4 years ago
I found that this is how Annotation.merge
works:
def merge(self, other):
if self.inherit is False:
return self
return self.__class__(
self.options + other.options,
inherit=other.inherit,
apply=self.apply if self.apply is not None else other.apply,
)
As I see, it's used to merge mutliple decorators as well as inherited values. Furthermore, if I set inherit=False
in the first decorator, second will be ignored (in docs and results).
I have the same issue and was puzzled for a very long time because my other methods worked, except for PUT
.
My main issue is that for responses without content and therefore no schema I can't use apply because then the None
in the @marshal_with(None, ...)
decorator will be converted into a dict
later on (default value for empty schema) and the schema dumping will raise an error because the dict
is no schema ...
The only solution I can think of is just using the schema myself to dump the object.
Another way (just to handle None
-Schema or empty results) would be an additional check in wrapper.py:L58
(?) marshal_result(self, result, status_code)
. Here a further check before dumped = schema.dump(result)
whether the schema
object has a dump(..)
method might be enough. That's unfortunately only a dirty fix (possibly?) but I'm not sure what other
No real solution for different schemas with different None
-schemas can be supplied to the @marshal_with
decorator that will require apply=False
and where the functions return non-easily serializable objects. (like no JSON, string, numbers etc.)apply
states.
My use case below but only the PUT
method fails. (Either because apply=True
for None
-Schema or apply=False
for my TaskSchema
where the Task
-Resource is not serialized.)
@doc(tags=["api"])
class TaskResourceById(MethodResource):
endpoint = "task"
@doc(description="A task.")
@marshal_with(TaskSchema, description="Get task with id.")
def get(self, task_id):
return self._get_task(task_id)
@use_kwargs(TaskSchema)
@marshal_with(TaskSchema, description="Update task.", apply=True)
@marshal_with(None, code=404, description="No task found.", apply=False)
def put(self, task_id, **kwargs):
task = self._get_task(task_id)
if not task:
return None, 404
for key, value in kwargs.items():
setattr(task, key, value)
local_object = db.session.merge(task)
db.session.add(local_object)
db.session.commit()
return (
local_object,
200,
{
"location": url_for(
f"{bp_api.name}.{TaskResourceById.endpoint}",
task_id=local_object.id,
)
},
)
@marshal_with(None, code=204, description="Task deleted.", apply=False)
@marshal_with(None, code=404, description="No task found.", apply=False)
def delete(self, task_id):
task = self._get_task(task_id)
if not task:
return None, 404
local_object = db.session.merge(task)
db.session.delete(local_object)
db.session.commit()
return None, 204
# --------------------------------
def _get_task(self, task_id):
return Task.query.get(task_id)
# --------------------------------
The error messages for PUT
depending on apply
:
...
File "***/venv/lib/python3.7/site-packages/flask_apispec/wrapper.py", line 37, in __call__
mv = self.marshal_result(rv, status_code)
File "***/venv/lib/python3.7/site-packages/flask_apispec/wrapper.py", line 67, in marshal_result
dumped = schema.dump(result)
AttributeError: 'dict' object has no attribute 'dump'
...
File "***/venv/lib/python3.7/site-packages/flask/json/__init__.py", line 100, in default
return _json.JSONEncoder.default(self, o)
File "/usr/lib/python3.7/json/encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type Task is not JSON serializable
Let's assume we declare a resource (for any reason) like this:
I expected that TheTestSchema will be used in default case, and TheSecondSchema is present only in docs - and it looks in docs like this. But in fact, no one schema is used, in any case. I think
apply=False
is propagated to allmarshal_with
decorators for given method.