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

Add type annotations to journalist_app.api #5464

Merged

Conversation

nabla-c0d3
Copy link
Contributor

@nabla-c0d3 nabla-c0d3 commented Aug 22, 2020

Status

Ready for review.

Description of Changes

To help with #4399 , this PR adds type annotations to journalist_app/api.py

auth_token = request.headers.get('Authorization').split(" ")[1]
user = Journalist.validate_api_token_and_get_user(auth_token)
return user
def _authenticate_user_from_auth_header(request: flask.Request) -> Journalist:
Copy link
Contributor Author

@nabla-c0d3 nabla-c0d3 Aug 22, 2020

Choose a reason for hiding this comment

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

There was code to parse the Authorization header in both get_user_object() and token_required(), so i combined them into just one function (because get_user_object() was causing a lot typing errors).

I would suggest passing the resulting/authenticated user as an argument to functions decorated with @token_require so that the views that need to know who the current user is don't need to parse the Auth header again (as done in all_source_replies() for example).
I can make the change if that's useful.

Copy link
Contributor

Choose a reason for hiding this comment

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

It would generally be better to make functional changes in separate PRs, particularly when they're optimizations and involve authentication, instead of mixing the refactoring with the type hinting.

I preferred the old name, get_user_object: it was accurate, if less precise, and a lot shorter.

Copy link
Contributor

@kushaldas kushaldas left a comment

Choose a reason for hiding this comment

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

At least for 3 people, including my local laptop: the branch is throwing this following error:

✦ ❯ make  typelint
███ Running mypy type checking...
securedrop/journalist_app/api.py:200: error: Incompatible return value type (got "werkzeug.wrappers.Response", expected "flask.wrappers.Response")
securedrop/journalist_app/api.py:209: error: Incompatible return value type (got "werkzeug.wrappers.Response", expected "flask.wrappers.Response")
Found 2 errors in 1 file (checked 104 source files)
make: *** [Makefile:124: typelint] Error 1

@kushaldas
Copy link
Contributor

@emkll please have a look at this, I am okay to approve, but want to know why a few of us saw that error.

@nabla-c0d3
Copy link
Contributor Author

Didn't have this error locally but I can fix it anyway, later today or tomorrow.

@kushaldas
Copy link
Contributor

Didn't have this error locally but I can fix it anyway, later today or tomorrow.

Yes, I can see this in my laptop, but not in the other places. I can not even see this in the CI. You don't have to change anything right now. But, we should figure out why are we seeing this in some machines.

@emkll
Copy link
Contributor

emkll commented Sep 15, 2020

Strange, I too am seeing the same error @kushaldas is describing. Those errors occur when running make typelint locally, either in a virtual environment or in the dev container. Looks like typelint is passing in CI. I double checked the version, they are the same in both CI and in my local virtualenv:

mypy==0.761
mypy-extensions==0.4.3

We should definitely resolve this discrepancy prior to merging.

@nabla-c0d3 if you want to take a stab at resolving the issue, that would be greatly appreciated. Otherwise we would be happy to investigate on your behalf, just let us know. Thank you for your continued contributions to the project!

@nabla-c0d3
Copy link
Contributor Author

I haven't been able to reproduce this error. I do get the same mypy error message if I replace:

def serve_file_with_etag(db_obj: Union[Reply, Submission]) -> flask.Response:

with

def serve_file_with_etag(db_obj: Union[Reply, Submission]) -> werkzeug.Response:

which then returns the error:

$ make typelint
Running mypy type checking...
securedrop/journalist_app/api.py:200: error: Incompatible return value type (got "werkzeug.wrappers.Response", expected "flask.wrappers.Response")
securedrop/journalist_app/api.py:209: error: Incompatible return value type (got "werkzeug.wrappers.Response", expected "flask.wrappers.Response")
Found 2 errors in 1 file (checked 104 source files)
make: *** [typelint] Error 1

However this branch does have the correct flask.Response as the return type, which doesn't trigger the error in CI and on my computer.

@nabla-c0d3
Copy link
Contributor Author

I would suggest merging this PR (so I can continue adding type annotations elsewhere), and then investigating as part of #5516.

@emkll emkll force-pushed the add-types-to-api-in-journalist-app branch from a2b899c to 5ae517b Compare September 21, 2020 15:57
@emkll
Copy link
Contributor

emkll commented Sep 21, 2020

Sorry for the delay @nabla-c0d3 . The source of the confusion on my end is likely related to the merge commit that included this change as part of conflict resolution in a041434

I've taken the liberty of squashing/rebasing this branch on latest develop (removing merge commits) for a final round of CI. make typelint is now passing locally and in https://github.com/freedomofpress/securedrop/compare/rebase-5464

Copy link
Contributor

@emkll emkll left a comment

Choose a reason for hiding this comment

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

Thanks for the changes and for your patience @nabla-c0d3 the changes looks good to me, with linting passing locally and CI still passing.

To aid the review of future PR's, it would be helpful if you could:

  • Squash/rebase on latest develop instead of merging into your branch (though we would be happy to rebase on your behalf, let us know if you'd prefer to do it yourself)
  • Either limit or separate code refactoring from type annotations as much as possible

Thanks again for your contributions! We'll take a look at #5491 next.

result = model.query.filter(column == object_id).one_or_none()
else:
result = model.query.get(object_id)
def get_or_404(model: db.Model, object_id: str, column: str) -> db.Model:
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like we always specify column as parameter here, so lgtm

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed.

auth_token = request.headers.get('Authorization').split(" ")[1]
user = Journalist.validate_api_token_and_get_user(auth_token)
return user
def _authenticate_user_from_auth_header(request: flask.Request) -> Journalist:
Copy link
Contributor

Choose a reason for hiding this comment

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

It would generally be better to make functional changes in separate PRs, particularly when they're optimizations and involve authentication, instead of mixing the refactoring with the type hinting.

I preferred the old name, get_user_object: it was accurate, if less precise, and a lot shorter.

result = model.query.filter(column == object_id).one_or_none()
else:
result = model.query.get(object_id)
def get_or_404(model: db.Model, object_id: str, column: str) -> db.Model:
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed.

if request.method == 'GET':
source = get_or_404(Source, source_uuid, column=Source.uuid)
return jsonify(source.to_json()), 200
elif request.method == 'DELETE':
source = get_or_404(Source, source_uuid, column=Source.uuid)
utils.delete_collection(source.filesystem_id)
return jsonify({'message': 'Source and submissions deleted'}), 200
else:
abort(404)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a good addition, as Flask's implicit support for HEAD means we're setting up a 500 error when we fall out of the if without handling it, but I think a 405 (method not allowed) status would be more accurate here and the other places you've caught this.

@emkll emkll merged commit 98153cf into freedomofpress:develop Sep 21, 2020
@nabla-c0d3 nabla-c0d3 deleted the add-types-to-api-in-journalist-app branch October 24, 2020 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants