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

[DEV-11572] add: backwards compat static typing #303

Open
wants to merge 23 commits into
base: master
Choose a base branch
from

Conversation

thearchitector
Copy link
Contributor

@thearchitector thearchitector commented Apr 3, 2024

Summary

type stubs and mypy compliance. also ran our standard set of pre-commit linters against everything

enables using the client in typed contexts (all of Insights) with in-editor sanity checks without doing nasty type casts or ignores:

client: IndicoClient = IndicoClient(IndicoConfig(...))

# this is valid
subs: list[Submission] = client.call(ListSubmission(...))
# this is also valid
subs: Iterator[list[Submission]] = client.paginate(ListSubmission(...))

# this is not, and will throw a typing error 
subs: list[str] = client.call(ListSubmission(...))

also works for the async client, example in examples/typed_calls.py


enabled by turning http request classes into a generic that exposes its anticipated return type. for ex:

class GetIPAVersion(GraphQLRequest[str]):
    query = """
        "query getIPAVersion {
            ipaVersion
        }
    """

    def __init__(self) -> None:
        super().__init__(self.query)

    def process_response(self, response: "Payload") -> str:
        version: str = super().parse_payload(response)["ipaVersion"]
        return version

All typing should be compatible with the versions we say we test against (3.8+), and should not cause any breaking changes for people who are not using static typing.

verifications of that claim -- i made no material changes to any of the existing tests, only the linting:

tox:
  py38: OK (33.86=setup[31.30]+cmd[2.04,0.52] seconds)
  py39: OK (41.70=setup[38.93]+cmd[2.22,0.56] seconds)
  py310: OK (42.14=setup[39.36]+cmd[2.30,0.49] seconds)
  py311: OK (41.73=setup[39.30]+cmd[1.91,0.53] seconds)
  py312: OK (41.80=setup[38.82]+cmd[2.47,0.51] seconds)
  congratulations :) (42.20 seconds)
mypy:
(venv) indico@indico-XPS-13-9310:~/repos/indico-client-python$ mypy --config-file=pyproject.toml
Success: no issues found in 54 source files
integration tests:

these tests were already failing

FAILED tests/integration/queries/test_custom_blueprint.py::test_register_blueprint - indico.errors.IndicoRequestError: Status: 400, Error: A ComponentType.CUSTOM_OUTPUT blueprint called Meowtput with footer version 2 already exists.
FAILED tests/integration/queries/test_model_group.py::test_model_group_progress - indico.errors.IndicoRequestError: Status: 400, Error: 'NoneType' object is not subscriptable
FAILED tests/integration/queries/test_questionnaire.py::test_get_examples - assert 0 == 3
FAILED tests/integration/queries/test_questionnaire.py::test_add_labels - TypeError: AddLabels.__init__() got an unexpected keyword argument 'dataset_id'
FAILED tests/integration/queries/test_workflow.py::test_workflow_submission[_input0] - AttributeError: 'Submission' object has no attribute 'deleted'
FAILED tests/integration/queries/test_workflow.py::test_workflow_submission[_input1] - AttributeError: 'Submission' object has no attribute 'deleted'

vs. the run with my changes:

FAILED tests/integration/queries/test_custom_blueprint.py::test_register_blueprint - indico.errors.IndicoRequestError: Status: 400, Error: A ComponentType.CUSTOM_OUTPUT blueprint called Meowtput with footer version 2 already exists.
FAILED tests/integration/queries/test_model_group.py::test_model_group_progress - indico.errors.IndicoRequestError: Status: 400, Error: 'NoneType' object is not subscriptable
FAILED tests/integration/queries/test_questionnaire.py::test_get_examples - assert 0 == 3
FAILED tests/integration/queries/test_questionnaire.py::test_add_labels - TypeError: AddLabels.__init__() got an unexpected keyword argument 'dataset_id'
FAILED tests/integration/queries/test_workflow.py::test_workflow_submission[_input0] - AttributeError: 'Submission' object has no attribute 'deleted'
FAILED tests/integration/queries/test_workflow.py::test_workflow_submission[_input1] - AttributeError: 'Submission' object has no attribute 'deleted'
================================================================= 6 failed, 109 passed, 1 skipped in 3946.57s (1:05:46) =================================================================

@thearchitector thearchitector force-pushed the elias/typing branch 3 times, most recently from 518c533 to 5d24263 Compare April 5, 2024 04:24

return raw_response["data"]

def process_response(self, response: "AnyDict") -> "ResponseType":
Copy link
Contributor Author

@thearchitector thearchitector Apr 5, 2024

Choose a reason for hiding this comment

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

typed python does not permit overriding the typing of the signature in subclasses. we were doing that implicitly before by having this fn return Dict and subclasses return their BaseType, etc.

a Union[Dict, ResponseType] does not solve the problem here either, as subclasses would have had to cast or type: ignore to deal with the Dict part in their overrides (ugly, not nice)

the smallest code change soln I could come up with is to split the function into a parse_payload fn typed to return Any, and replaced all super().process_response calls in every query with super().parse_payload. this impl calls parse_payload to maintain backwards compat

practically, that means we're deprecating the use of super().process_response in any custom GraphQLRequest subclasses; i tried, but sadly could not figure out a nice way to raise a deprecation error

Copy link
Contributor

Choose a reason for hiding this comment

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

We've used https://deprecation.readthedocs.io/en/latest/ in the past for raising deprecation errors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea, i just couldn't figure out a way to only raise the error when invoking it via super().process_response

Copy link
Contributor

Choose a reason for hiding this comment

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

my kingdom for working deprecation in python

@thearchitector thearchitector changed the title Elias/typing [DEV-11572] add: backwards compat static typing Apr 5, 2024
@thearchitector thearchitector marked this pull request as ready for review April 5, 2024 23:10
@thearchitector thearchitector requested a review from a team as a code owner April 5, 2024 23:10
@@ -28,18 +37,22 @@ def valid_type(v):
)


# TODO: all the introspection here class makes the typing sus
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we can do away with this class at some point

Copy link
Contributor Author

Choose a reason for hiding this comment

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

moving to pydantic is my next pet project

Copy link
Contributor

Choose a reason for hiding this comment

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

cooldown collab?

jacobmanderson
jacobmanderson previously approved these changes Apr 10, 2024
indico/client/client.py Outdated Show resolved Hide resolved
jacobmanderson
jacobmanderson previously approved these changes Apr 11, 2024
@goatrocks
Copy link
Contributor

we can cut a pre-release and have some alpha/uat testing done here @nicholas-lockhart I think it would be worth it if folks have time to help test?

@madisonmay
Copy link
Contributor

we should get this merged relatively soon so it doesn't rot on the vine

@goatrocks goatrocks mentioned this pull request Jul 11, 2024
11 tasks
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.

None yet

5 participants