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
style(mypy): Enforcing typing for superset.views module #9939
style(mypy): Enforcing typing for superset.views module #9939
Conversation
metric_class: Optional[Type] = None # link to derivative of BaseMetric | ||
|
||
@property | ||
def column_class(self) -> Type: |
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 is done to ensure that the column_class
etc. is not-optional.
form_data=form_data, | ||
force=False, | ||
) | ||
if slc: |
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's a number of places where slc
could be None
.
"""Save or overwrite a slice""" | ||
slice_name = args.get("slice_name") | ||
action = args.get("action") | ||
slice_name = request.args.get("slice_name") |
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 need to parse args
which was merely request.args
when request
is global.
@@ -2071,9 +2094,6 @@ def sqllab_viz(self): | |||
cols = [] | |||
for config in data.get("columns"): | |||
column_name = config.get("name") | |||
SqlaTable = ConnectorRegistry.sources["table"] |
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 abstraction seemed necessary given we're restricting the models to the SQLA connector.
Codecov Report
@@ Coverage Diff @@
## master #9939 +/- ##
=======================================
Coverage 71.39% 71.39%
=======================================
Files 585 585
Lines 30968 30982 +14
Branches 3261 3261
=======================================
+ Hits 22109 22121 +12
- Misses 8750 8752 +2
Partials 109 109
Continue to review full report at Codecov.
|
f0670ef
to
07d9986
Compare
@@ -1831,15 +1858,15 @@ def publish(self, dashboard_id): | |||
return json_success(json.dumps({"published": dash.published})) | |||
|
|||
@has_access | |||
@expose("/dashboard/<dashboard_id>/") | |||
def dashboard(self, dashboard_id): | |||
@expose("/dashboard/<dashboard_id_or_slug>/") |
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.
dashboard_id_or_slug
seemed to be a more accurate representation of the field.
07d9986
to
f3129b7
Compare
@villebro et al. would you mind taking a look at this PR? |
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 a beauty.
a few small comments, but otherwise LGTM.
superset/views/base.py
Outdated
@@ -154,14 +157,14 @@ def wraps(self, *args, **kwargs): | |||
return functools.update_wrapper(wraps, f) | |||
|
|||
|
|||
def handle_api_exception(f): | |||
def handle_api_exception(f: Callable) -> Callable: |
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.
Maybe we can be a bit more precise about Callable
throughout? Something like Callable[..., FlaskResponse]
?
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.
Across the code base Callable
is one type which we haven't been more explicit about in terms of arguments and the return type. I wonder for simplicity whether these can remain Callable
and in a later PR add the return type to all Callable
types.
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 it's probably a good idea to add the full signature where it is simple, and leave as just Callable
if it would require extensive research or unions.
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.
First pass comments. If all my comments are irrelevant, I'm fine merging as-is 👍
superset/views/base.py
Outdated
@@ -154,14 +157,14 @@ def wraps(self, *args, **kwargs): | |||
return functools.update_wrapper(wraps, f) | |||
|
|||
|
|||
def handle_api_exception(f): | |||
def handle_api_exception(f: Callable) -> Callable: |
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 it's probably a good idea to add the full signature where it is simple, and leave as just Callable
if it would require extensive research or unions.
@@ -744,7 +769,7 @@ def explore_json(self, datasource_type=None, datasource_id=None): | |||
) | |||
|
|||
viz_obj = get_viz( | |||
datasource_type=datasource_type, | |||
datasource_type=cast(str, datasource_type), |
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.
does this really need to be cast
to str
? I'm assuming this is always non-null.
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.
@villebro this is part of the complexity I discussed during the meetup lastweek here, i.e., complex logic leads to a number of states which aren't trivial to digest without thoroughly reading through the code.
Under certain circumstances datasource_type
can be None
however in this context it will never to None
hence the reason for the cast(...)
which per the documentation states,
... give the type checker a little help when it can’t quite understand what is going on.
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.
Ok, makes sense.
@@ -834,7 +861,7 @@ def explore(self, datasource_type=None, datasource_id=None): | |||
return redirect(error_redirect) | |||
|
|||
datasource = ConnectorRegistry.get_datasource( | |||
datasource_type, datasource_id, db.session | |||
cast(str, datasource_type), datasource_id, db.session |
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.
Same as above
slc, | ||
slice_add_perm, | ||
slice_overwrite_perm, | ||
slice_download_perm, | ||
datasource_id, | ||
datasource_type, | ||
cast(str, datasource_type), |
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.
and here
dash = cast( | ||
Dashboard, | ||
db.session.query(Dashboard) | ||
.filter_by(id=int(request.args.get("save_to_dashboard_id"))) | ||
.one() | ||
.filter_by(id=int(request.args["save_to_dashboard_id"])) | ||
.one(), |
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.
Does this really need to be cast? I thought one()
knows it will always be Dashboard
.
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.
@villebro for some reason it think's it's an Optional[Dashboard]
(possibly due to the type declaration on line 1040).
dfe196c
to
900a03b
Compare
900a03b
to
dfee72a
Compare
Thanks @serenajiang and @villebro for the review. I believe I've addressed all your comments and thus this is ready for re-review. |
Co-authored-by: John Bodley <john.bodley@airbnb.com> (cherry picked from commit 36627af)
Co-authored-by: John Bodley <john.bodley@airbnb.com>
Co-authored-by: John Bodley <john.bodley@airbnb.com>
SUMMARY
Adding the
mypy
type enforcement for the remainder of thesuperset.views
module. Note this PR is somewhat hairier that the previous versions, in part because of the brittle nature of many of the base views.Note there were many views which were using the
request.args.get(...)
pattern meaning the result was of typeOptional[Any]
which would actually throw an exception if the result wasNone
for functions likejson.loads(...)
. Given that these functions aren't throwing I opted to userequest.args[...]
where possible as there seems to be an implicit definition that these values are always defined.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TEST PLAN
CI.
ADDITIONAL INFORMATION