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

✨ Demographic resource and tests #62

Merged
merged 1 commit into from
Feb 1, 2018
Merged

Conversation

znatty22
Copy link
Member

@znatty22 znatty22 commented Jan 22, 2018

Add a crud api for the demographic entity (get, post, put, delete methods)
Add testing crud api

@znatty22 znatty22 changed the title ✨ Add the demographic entity to the Kids First Dataservice ✨ Add the demographic entity to the Kids First Dataservice Jan 22, 2018
"""
API level error handling/message formatting

Includes helpers to parse the generic database integrity error
Copy link
Member Author

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)
Copy link
Member Author

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

@znatty22 znatty22 added feature New functionality data model Changes to the underlying data representation labels Jan 22, 2018


# TODO
"""
Copy link
Member Author

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": [
    ...
  ]
}
```

Copy link
Contributor

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',
Copy link
Member Author

@znatty22 znatty22 Jan 22, 2018

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

Copy link
Contributor

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.

@znatty22 znatty22 self-assigned this Jan 22, 2018
Copy link
Contributor

@dankolbman dankolbman left a 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.



# TODO
"""
Copy link
Contributor

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):
Copy link
Contributor

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?

Copy link
Contributor

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',
Copy link
Contributor

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)
Copy link
Contributor

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')
Copy link
Contributor

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?

Copy link
Member Author

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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

participant_id?

Copy link
Member Author

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)
Copy link
Contributor

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.

@znatty22
Copy link
Member Author

znatty22 commented Jan 22, 2018

Thanks for the comments/suggestions. Let's discuss more on error handling and response formatting

@znatty22
Copy link
Member Author

znatty22 commented Jan 22, 2018

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
Copy link
Member Author

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.

Copy link
Contributor

@dankolbman dankolbman left a 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No database checking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, will add 👍

@znatty22 znatty22 changed the title ✨ Add the demographic entity to the Kids First Dataservice ✨ Demographic resource and tests Jan 31, 2018
@znatty22 znatty22 force-pushed the demographic-entity branch 2 times, most recently from b68a3ca to c3862e3 Compare January 31, 2018 22:54
Copy link
Contributor

@parimalak parimalak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@dankolbman dankolbman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good!

@znatty22 znatty22 merged commit 9d35767 into master Feb 1, 2018
@znatty22 znatty22 deleted the demographic-entity branch February 2, 2018 00:04
@dankolbman dankolbman mentioned this pull request Apr 2, 2018
alubneuski pushed a commit that referenced this pull request Oct 11, 2024
✨ Demographic resource and tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
data model Changes to the underlying data representation feature New functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants