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

Improve type checking in query pagination and AST manipulation code. #898

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

obi1kenobi
Copy link
Contributor

Includes type-level fixes for handling InlineFragmentNode AST values in query pagination code, a couple of new helper functions, and type hints for the AST manipulation module.

Based on the tests passing and my careful refactoring, I am fairly confident that I haven't made pagination any worse. However, I opened #897 to verify that query pagination in the presence of type coercions (signaled by InlineFragmentNode AST values) actually works.

This is quite a tricky PR, so I'd like to have @bojanserafimov review it before we merge, in addition to anyone else that might want to check it out.

Includes type-level fixes for handling InlineFragmentNode AST values in query pagination code, a couple of new helper functions, and type hints for the AST manipulation module.
@obi1kenobi obi1kenobi added enhancement maintainer quality-of-life Features that ease development, but are not necessarily visible to package users. labels Aug 11, 2020
@codecov
Copy link

codecov bot commented Aug 11, 2020

Codecov Report

Merging #898 into main will decrease coverage by 0.09%.
The diff coverage is 90.32%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #898      +/-   ##
==========================================
- Coverage   95.07%   94.98%   -0.10%     
==========================================
  Files         111      111              
  Lines        8788     8767      -21     
==========================================
- Hits         8355     8327      -28     
- Misses        433      440       +7     
Impacted Files Coverage Δ
...l_compiler/query_pagination/query_parameterizer.py 93.96% <84.61%> (-3.21%) ⬇️
graphql_compiler/ast_manipulation.py 89.83% <90.00%> (-2.17%) ⬇️
graphql_compiler/global_utils.py 98.41% <100.00%> (+0.16%) ⬆️
...l_compiler/query_pagination/pagination_planning.py 92.06% <100.00%> (ø)
graphql_compiler/typedefs.py 85.71% <0.00%> (-14.29%) ⬇️
graphql_compiler/schema_generation/schema_graph.py 77.37% <0.00%> (-1.71%) ⬇️
graphql_compiler/compiler/filters.py 94.07% <0.00%> (-0.30%) ⬇️
graphql_compiler/schema/schema_info.py 89.36% <0.00%> (-0.23%) ⬇️
graphql_compiler/cost_estimation/statistics.py 92.85% <0.00%> (-0.20%) ⬇️
... and 6 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 5c4e232...259d287. Read the comment docs.

Copy link
Collaborator

@bojanserafimov bojanserafimov 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. We can take the inline fragment discussion to #897

I have a nit.

return ast


def assert_selection_is_a_field_node(ast: Any) -> FieldNode:
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's strange for an assert function to return, though I get why it has to.

In relation to mypy's cast function, this is just a safe_cast. If you don't find mypy's use of the word cast repulsive, this would be a good name for this function. We can even make it generic: safe_cast(value: Any, type: Type[T]) -> T.

I personally don't like mypy's naming of cast since there's no change of shape involved (like in metal casting). It should have been called unsafe_assume_type. But the function exists, there's nothing I can do about it, and calling our function safe_cast makes it a discoverable alternative to cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like the idea — perhaps checked_cast as an alternative name, to make the answer to "how is this safer" a bit more explicit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

checked_cast() doesn't work with Union because Union is not a Type, so Type[T] doesn't match.

For bounded-length tuples, this is achievable:

def checked_cast_to_union2(target_types: Tuple[Type[T_A], Type[T_B]], value: Any) -> Union[T_A, T_B]:
    if not isinstance(value, target_types):
        raise AssertionError()

    return value

I don't know of a way to make target_types: Tuple[Type, ...] work and connect the types it contains to the types inside the Union result.

Given that we're somewhat unlikely to have unions of more than like 3 or so things, I'm fine with adding checked_cast_to_union2() and in the future potentially adding a checked_cast_to_union3() function that takes a 3-tuple as necessary.

What do you think?

Comment on lines 254 to 256
# N.B.: We cannot use assert_selection_is_a_field_or_inline_fragment_node() here, since
# that function's return type annotation is a Union type that isn't compatible
# with the generic type signature of _add_pagination_filter_at_node().
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting how assert_selection_is_a_field_or_inline_fragment_node works with unions: "We used to know x is of type A, but we ran some assertions and now we're not sure if it's A or B"

Can't think of a cleaner solution.

Btw, we use N.B. and NOTE in the codebase. No need to standardize, but letting you know that NOTE gets highlighted by many text editors, just like TODO is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If checked_cast() works out, we might not need this note anymore. 🤞

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wrote a checked_cast_to_union2() function but it turns out that even it can't do the trick with generics. I updated the note with my new observations, let me know if you want to talk about it.

@obi1kenobi
Copy link
Contributor Author

@bojanserafimov I'd love a re-review here when you get the chance.

@bojanserafimov bojanserafimov removed their request for review April 12, 2022 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement maintainer quality-of-life Features that ease development, but are not necessarily visible to package users.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants