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

style(mypy): Enforcing typing for superset.views module #9939

Conversation

john-bodley
Copy link
Member

@john-bodley john-bodley commented May 28, 2020

SUMMARY

Adding the mypy type enforcement for the remainder of the superset.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 type Optional[Any] which would actually throw an exception if the result was None for functions like json.loads(...). Given that these functions aren't throwing I opted to use request.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

  • Has associated issue:
  • Changes UI
  • Requires DB Migration.
  • Confirm DB Migration upgrade and downgrade tested.
  • Introduces new feature or API
  • Removes existing feature or API

metric_class: Optional[Type] = None # link to derivative of BaseMetric

@property
def column_class(self) -> Type:
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 is done to ensure that the column_class etc. is not-optional.

form_data=form_data,
force=False,
)
if slc:
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'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")
Copy link
Member Author

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"]
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 abstraction seemed necessary given we're restricting the models to the SQLA connector.

@codecov-commenter
Copy link

codecov-commenter commented May 28, 2020

Codecov Report

Merging #9939 into master will increase coverage by 0.00%.
The diff coverage is 85.27%.

Impacted file tree graph

@@           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           
Flag Coverage Δ
#cypress 54.00% <ø> (-0.08%) ⬇️
#javascript 59.39% <ø> (ø)
#python 71.61% <85.27%> (+0.03%) ⬆️
Impacted Files Coverage Δ
superset/sql_validators/presto_db.py 82.89% <ø> (ø)
superset/views/base_schemas.py 0.00% <0.00%> (ø)
superset/viz.py 71.94% <ø> (ø)
superset/viz_sip38.py 0.00% <0.00%> (ø)
superset/connectors/base/models.py 88.92% <66.66%> (-0.89%) ⬇️
superset/views/api.py 66.66% <75.00%> (+0.95%) ⬆️
superset/views/schedules.py 60.86% <83.33%> (-0.04%) ⬇️
superset/views/core.py 76.49% <86.16%> (-0.11%) ⬇️
superset/views/tags.py 40.74% <88.88%> (+1.50%) ⬆️
superset/connectors/druid/models.py 82.47% <100.00%> (ø)
... and 17 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 244677c...dfee72a. Read the comment docs.

@john-bodley john-bodley force-pushed the john-bodley--mypy-enforcement-views-remainder branch 2 times, most recently from f0670ef to 07d9986 Compare May 28, 2020 19:37
@@ -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>/")
Copy link
Member Author

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.

@john-bodley john-bodley force-pushed the john-bodley--mypy-enforcement-views-remainder branch from 07d9986 to f3129b7 Compare May 28, 2020 20:02
@john-bodley john-bodley changed the title style(mypy): Enforcing typing for superset.views style(mypy): Enforcing typing for superset.views module May 29, 2020
@john-bodley
Copy link
Member Author

john-bodley commented Jun 1, 2020

@villebro et al. would you mind taking a look at this PR?

Copy link
Contributor

@serenajiang serenajiang left a 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/sql_validators/presto_db.py Outdated Show resolved Hide resolved
superset/views/base.py Show resolved Hide resolved
superset/viz.py Outdated Show resolved Hide resolved
@@ -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:
Copy link
Contributor

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]?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member

@villebro villebro left a 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 👍

@@ -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:
Copy link
Member

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

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.

Copy link
Member Author

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.

Copy link
Member

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

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

Choose a reason for hiding this comment

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

and here

Comment on lines +1044 to +1048
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(),
Copy link
Member

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.

Copy link
Member Author

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).

@john-bodley john-bodley force-pushed the john-bodley--mypy-enforcement-views-remainder branch 5 times, most recently from dfe196c to 900a03b Compare June 3, 2020 22:59
@john-bodley john-bodley force-pushed the john-bodley--mypy-enforcement-views-remainder branch from 900a03b to dfee72a Compare June 3, 2020 23:31
@john-bodley
Copy link
Member Author

Thanks @serenajiang and @villebro for the review. I believe I've addressed all your comments and thus this is ready for re-review.

@john-bodley john-bodley requested a review from villebro June 4, 2020 15:51
@john-bodley john-bodley merged commit 63e0188 into apache:master Jun 5, 2020
@john-bodley john-bodley deleted the john-bodley--mypy-enforcement-views-remainder branch June 5, 2020 15:44
john-bodley added a commit to john-bodley/superset that referenced this pull request Jun 10, 2020
john-bodley added a commit that referenced this pull request Jun 10, 2020
Co-authored-by: John Bodley <john.bodley@airbnb.com>
graceguo-supercat pushed a commit to airbnb/superset-fork that referenced this pull request Jun 10, 2020
Co-authored-by: John Bodley <john.bodley@airbnb.com>
(cherry picked from commit 36627af)
auxten pushed a commit to auxten/incubator-superset that referenced this pull request Nov 20, 2020
Co-authored-by: John Bodley <john.bodley@airbnb.com>
auxten pushed a commit to auxten/incubator-superset that referenced this pull request Nov 20, 2020
Co-authored-by: John Bodley <john.bodley@airbnb.com>
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 0.37.0 labels Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels size/XL 🚢 0.37.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants