-
Notifications
You must be signed in to change notification settings - Fork 3
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
✨ Demographic resource and tests #62
Conversation
""" | ||
API level error handling/message formatting | ||
|
||
Includes helpers to parse the generic database integrity error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There doesn't seem to be a better way to distinguish specific types of integrity errors. If someone else knows of anything better please let me know.
context = {'method': 'create', 'entity': 'demographic', | ||
'ref_entity': 'participant', 'exception': e, | ||
'payload': body} | ||
# raise BadInputError(context=context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left this comment in here to show that error handling could also be handled with custom exceptions. With this approach, we would throw a custom exception and then handle it at the API level using the api.errorhandler decorator. Decided to avoid this approach for now due to some related known bugs with flask_restplus errorhandling. See https://github.com/noirbizarre/flask-restplus/issues
dataservice/api/__init__.py
Outdated
|
||
|
||
# TODO | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a work around we may need to change our kf_response format a little bit to match the default restplus format. So instead of having
{
"_links": {
...
},
"_status": {
"code": 200,
"message": "Success"
},
"results": [
...
]
}
```
We might need to change to something like:
{
"_links": {
...
},
"code": 200,
"message": "Success"
"results": [
...
]
}
```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, kf_response
is a little restricted in what it actually applies to. I'm trying to move it down into the Resource
but it may have to go deeper. For now, extending the Api with a modified error_handler
may be easiest.
# Catch error and rollback db | ||
except IntegrityError as e: | ||
db.session.rollback() | ||
context = {'method': 'create', 'entity': 'demographic', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth considering the crud mixin or something like it :). Reason being, if we had something to encapsulate our crud methods we could handle the db errors in that class and then avoid passing context parameters to another module (error_handler). The crud class would still call out to the error_handler to determine the type of error but it could take care of forming the actual error message with the context (crud method used, the entity, the reference entity, etc). Would also avoid duplicate code in every entity resource class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that exception handling from werkzeug and flask already encapsulate these things pretty well. It seems like the functionality here could be achieved by coercing to the standard error handling format, or by extending it as needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a lot of the abstractions your suggesting could be taken care of by extending the response handling in flask and flask-restplus to fit our needs. This approach seems more natural to me as it would still use the same familiar interface of a flask application rather than a high level library that ultimately deals with low level functionality.
I've been looking at how the resource responses could be handled here but there's some nuances with restplus' marshaling that make it difficult. It may be better to look more deeply into flask for how that may be achieved.
dataservice/api/__init__.py
Outdated
|
||
|
||
# TODO | ||
""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, kf_response
is a little restricted in what it actually applies to. I'm trying to move it down into the Resource
but it may have to go deeper. For now, extending the Api with a modified error_handler
may be easiest.
NOT_FOUND_MSG_TEMPLATE = 'Client error: {} not found' | ||
|
||
|
||
def handle_invalid_input(context=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this function trying to accomplish? It looks like it's for formatting the error response from SQLAlchemy? In that case, does this really need to happen, and if so, could it happen by extending the SQLAlchemy exceptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, maybe the context should be kwargs? That way there's some sort of 'type' checking on the handle.
# Catch error and rollback db | ||
except IntegrityError as e: | ||
db.session.rollback() | ||
context = {'method': 'create', 'entity': 'demographic', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that exception handling from werkzeug and flask already encapsulate these things pretty well. It seems like the functionality here could be achieved by coercing to the standard error handling format, or by extending it as needed.
kf_id=kf_id).one_or_none() | ||
|
||
if not demographic: | ||
return self._not_found(kf_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be refactored in the future. It seems the main reason we're doing it this way is to make our responses fit our desired format. If we can figure out how to extend the handling to use our response, this line could be as easy as raise HTTPException
if not demographic: | ||
return self._not_found(kf_id) | ||
|
||
demographic.external_id = body.get('external_id') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been thinking of moving the parsing functionality here to a from_json
or from_dict
on the ORM class. It Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, sounds like a good idea
'sex': fields.String( | ||
example='F', | ||
description='The sex of the person this demographic belongs to'), | ||
'person_id': fields.String( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
participant_id
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will update this PR now that #60 has been merged
# Check that demographic was created correctly | ||
self.assertEqual(Demographic.query.count(), 1) | ||
new_demographic = Demographic.query.first() | ||
self.assertGreater(new_demographic.created_at, dt) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably draw out all these created_at and modified_at checks to a parametrized test.
Thanks for the comments/suggestions. Let's discuss more on error handling and response formatting |
After discussing, we agreed that flask_restplus has too many issues with error handling and response marshaling. We've decided to go with regular flask instead of flask_restplus which will solve the error handling issues. And we are going with marshmallow to solve the request/response validation/formatting. See #63 for details. |
try: | ||
# Check if demographic exists | ||
d1 = Demographic.query.filter_by(kf_id=kf_id).one() | ||
# Deserialize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done more dynamically/better in the future. Tried using sqlalchemy session.merge with the loaded obj from marshmallow but this achieves patch/partial update behavior. To do a full replace, would first have to iterate over the d1's columns and relationships and set them back to defaults and then perform merge with the deserialized object from marshmallow. This isn't trivial so will look into this in the near future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like a lot of the test functions could easily be replaced with 1-2 lines added to the parametrized tests here
message = 'Cannot create demographic, participant already' | ||
self.assertIn(message, response['_status']['message']) | ||
|
||
# Check database |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No database checking?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, will add 👍
1cf797e
to
006c71f
Compare
b68a3ca
to
c3862e3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks good!
10e3831
to
78bc4b9
Compare
✨ Demographic resource and tests
Add a crud api for the demographic entity (get, post, put, delete methods)
Add testing crud api