Skip to content

[v8.0] Annotate ReturnValues.py #6309

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

Merged
merged 7 commits into from
Aug 19, 2022

Conversation

chaen
Copy link
Contributor

@chaen chaen commented Aug 11, 2022

I originally just wanted to silent pylint for complaining about unsubscriptable-object when using convertToReturnValue. Turns out to be trickier than expected: pylint-dev/pylint#7292

Anyway, I added the type hints. Not sure whether we want to merge it already or wait for pylint to be fixed. I don't mind.

Also, as of python 3.10, we will be able to forward the type hint through the elipse https://peps.python.org/pep-0612/

Update from @cburr: This now adds type hints to the entire ReturnValues module and can propogate the type of retVal["Value"] through S_OK/convertToReturnValue/returnValueOrRaise. We can't actually use mypy for checking this until python/mypy#13389 is merged and releasd however I've tested locally with that branch and it works as expected and is able to catch a bunch of errors.

Click me to see examples of errors than can now be caught with mypy
from typing import Union

from DIRAC.Core.Utilities.ReturnValues import convertToReturnValue, DReturnType, DOKReturnType, S_OK, returnValueOrRaise


def do_thing0(x: int) -> DReturnType[int]:
    return S_OK(x * 2)


def do_thing1(x: int) -> DOKReturnType[int]:
    return S_OK(x * 2)


@convertToReturnValue
def do_thing2(x: int) -> int:
    return x * 2


@convertToReturnValue
def do_thing3(x: str) -> str:
    return x * 2


def do_thing4(x: int) -> DReturnType[int]:
    if x > 20:
        return S_OK(x)
    return S_OK()
    # ↑ error: Incompatible return value type (got "DOKReturnType[None]", expected "Union[DOKReturnType[int], DErrorReturnType]")


def do_thing5(x: int) -> Union[DReturnType[int], DReturnType[None]]:
    if x > 20:
        return S_OK(x)
    return S_OK()


retVal = do_thing0(5)
if retVal["OK"]:
    print(retVal["Value"] + 1)
    print(retVal["Value"] + "1")
    # ↑ error: Unsupported operand types for + ("int" and "str")
else:
    print(retVal["Message"])


retVal = do_thing0(5)
print(retVal["Value"] + 1)
# ↑ error: TypedDict "DErrorReturnType" has no key "Value"

retVal = do_thing1(5)
print(retVal["Value"] + 1)  # Allowed as do_thing1 can't return DErrorReturnType


retVal = do_thing2(5)
if retVal["OK"]:
    print(retVal["Value"] + 1)
    print(retVal["Value"] + "1")
    # ↑ error: Unsupported operand types for + ("int" and "str")
else:
    print(retVal["Message"])


xxx = do_thing0(5)
thing0 = returnValueOrRaise(xxx)
print(thing0 + 1)
print(thing0 + "1")
# ↑ error: Unsupported operand types for + ("int" and "str")


thing2: int = returnValueOrRaise(do_thing2(5))
print(thing2 + 1)
print(thing2 + "1")
# ↑ error: Unsupported operand types for + ("int" and "str")


retVal = do_thing3("2")
if retVal["OK"]:
    print(retVal["Value"] + "1")
    print(retVal["Value"] + 1)
    # ↑ error: Unsupported operand types for + ("str" and "int")
else:
    print(retVal["Message"])


print(retVal["Value"])
# ↑ error: TypedDict "DErrorReturnType" has no key "Value"
print(retVal["Message"])
# ↑ error: TypedDict "DOKReturnType[str]" has no key "Message"


x = do_thing5(5)
if x["OK"]:
    value = x["Value"]
    if value:
        print(value + 3)
    print(value + 3)
    # ↑ error: Unsupported operand types for + ("None" and "int")
    # ↑ note: Left operand is of type "Optional[int]"

BEGINRELEASENOTES
*Core
NEW: Introduce DReturnType/DOKReturnType/DErrorReturnType for DIRAC return structures

ENDRELEASENOTES

@chrisburr chrisburr force-pushed the v8.0_FEAT_typeCheck branch from 1b283ce to bcf6ba0 Compare August 15, 2022 07:49
@chrisburr chrisburr changed the title [v8.0] Annotate convertToReturnType [v8.0] Annotate ReturnType.py Aug 15, 2022
@chrisburr chrisburr changed the title [v8.0] Annotate ReturnType.py [v8.0] Annotate ReturnValues.py Aug 15, 2022
@chrisburr chrisburr mentioned this pull request Aug 15, 2022
8 tasks
@chrisburr chrisburr marked this pull request as ready for review August 15, 2022 09:00
@chrisburr chrisburr force-pushed the v8.0_FEAT_typeCheck branch from b9749ac to a63a3f5 Compare August 17, 2022 06:53
@@ -5,14 +5,43 @@

keys are converted to string
"""
from __future__ import annotations
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed?

Copy link
Member

Choose a reason for hiding this comment

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

It fixes a handful of annoying quirks when adding types. It was planned to be the default in 3.10 and then 3.11 but it has now been delayed twice due to having a different set of issues. PEP-649 has some more details but for now I think from __future__ import annotations is the best course of action for DIRAC as it's more limited than the original approach. If we change our mind removing the future imports should be easier than the opposite approach.

Technically what it does it change the type hints to being evaluated on demand rather than at function definition time. For example:

class C:
    @classmethod
    def factory_func(cls) -> C:
        return cls()

By default this fails with NameError: name 'C' is not defined. Alteratively you can make the type hint a string which causes it's evaluation to be delayed:

class C:
    @classmethod
    def factory_func(cls) -> "C":
        return cls()

But this is quite ugly and annoying and makes evaluating the type hints harder. Using from __future__ import annotations makes it so that the type hint evaluation is always delayed. See [PEP-563](https://peps.python.org/pep-0563/ for details).

(Note this example isn't quite correct if you want correct typing for subclasses of C.)

Comment on lines +15 to +17
[tool.pylint.typecheck]
# List of decorators that change the signature of a decorated function.
signature-mutators = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this needed now? Are you just adding to not forget later on?

Copy link
Member

Choose a reason for hiding this comment

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

I defer to @chaen

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 pylint isn't complaining anymore, then we can remove it :-)

@chrisburr chrisburr force-pushed the v8.0_FEAT_typeCheck branch from a63a3f5 to d1d07c4 Compare August 17, 2022 08:22
@fstagni fstagni merged commit 8a0e60b into DIRACGrid:integration Aug 19, 2022
@DIRACGridBot DIRACGridBot added the sweep:ignore Prevent sweeping from being ran for this PR label Aug 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sweep:ignore Prevent sweeping from being ran for this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants